spring security - jwt request filter validates and authenticate session but principal is null - spring

After updating my spring boot from 2.0.3 to 2.2.1 somehow my Spring Security configuration stop working.
setup is as follow - I would like to have all my request processed by request filter where JWT token is validated and UserDetails are created. So config is pretty easy:
#Configuration
#EnableWebSecurity
public class NoAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
}
and filter class
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class JwtRequestFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(JwtRequestFilter.class);
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtUtils jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String requestTokenHeader = "";
try{
requestTokenHeader = WebUtils.getCookie(request, "token").getValue();
} catch (NullPointerException ex ){}
if (requestTokenHeader != null && requestTokenHeader.contains(".")) {
jwtToken = requestTokenHeader;
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
log.error("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
log.error("JWT Token has expired");
}
}
if (username != null ) {
UserDetails userDetails = null;
userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
chain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String[] AUTH_WHITELIST = {
"/v2/auth/",
"/api/packetdiscovery"
};
String path = request.getServletPath();
return (StringUtils.startsWithAny(path, AUTH_WHITELIST));
}
}
and by debugging I can see that everything work smoothly until request reach Controller where NullPointerException Is thrown on Principal
#PreAuthorize("hasAuthority('ROLE_USER')")
#GetMapping(value = "/userinfo")
public ResponseEntity<SessionOwner> getSesstionOwner(Principal user) {
return dashboardService.getSessionOwner(user.getName());
}
Could anyone give me advice on how to handle it?

Related

Migration to Ldap based authentication from Inmemory authentication

I have to implement a LDAP based authentication and return a JWT token in response which will be used in subsequent request.
I followed this guide InMemory Authentication with JWT to implement InMemory based Authentication. I know and tried standalone code for LDAP authentication and it's working.
While integrating ldap authentication in the code explained in the above link's example,I am getting stuck and not able to move forward.
I am not getting, How to define loadByUsername method of UserDetailsService class in case of ldap authentication.
Looking for some direction/guidance on this to proceed.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class JWTWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtUnAuthorizedResponseAuthenticationEntryPoint jwtUnAuthorizedResponseAuthenticationEntryPoint;
#Autowired
private JwtTokenAuthorizationOncePerRequestFilter jwtAuthenticationTokenFilter;
#Autowired
private Environment env;
#Value("${jwt.get.token.uri}")
private String authenticationPath;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new LdapAuthenticationProvider(env)).eraseCredentials(false);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(jwtUnAuthorizedResponseAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity
.headers()
.frameOptions().sameOrigin() //H2 Console Needs this setting
.cacheControl(); //disable caching
}
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity
.ignoring()
.antMatchers(
HttpMethod.POST,
authenticationPath
)
.antMatchers(HttpMethod.OPTIONS, "/**")
.and()
.ignoring()
.antMatchers(
HttpMethod.GET,
"/" //Other Stuff You want to Ignore
)
.and()
.ignoring()
.antMatchers("/h2-console/**/**");//Should not be in Production!
}
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
}
#Component
public class LdapAuthenticationProvider implements AuthenticationProvider
{
private Environment environment;
public LdapAuthenticationProvider(Environment environment) {
this.environment = environment;
}
private LdapContextSource contextSource;
private LdapTemplate ldapTemplate;
private void initContext(Authentication authentication)
{ contextSource = new LdapContextSource();
contextSource.setUrl(environment.getProperty("ldap.server.url"));
//contextSource.setAnonymousReadOnly(true);
contextSource.setUserDn("domain\\uid");
contextSource.setBase("DC=global,DC=comp,DC=org");
contextSource.setPassword("pwd");
contextSource.afterPropertiesSet();
ldapTemplate = new LdapTemplate(contextSource);
ldapTemplate.setIgnorePartialResultException(true);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
initContext(authentication);
Filter filter = new EqualsFilter("sAMAccountName", authentication.getName());
Boolean authenticate = ldapTemplate.authenticate(LdapUtils.emptyLdapName(), filter.encode(), authentication.getCredentials().toString());
if (authenticate)
{
UserDetails userDetails = new User(authentication.getName(), authentication.getCredentials().toString()
, new ArrayList<>());
Authentication auth = new UsernamePasswordAuthenticationToken(userDetails,
authentication.getCredentials().toString(), new ArrayList<>());
return auth;
}
else
{
return null;
}
}
#Override
public boolean supports(Class<?> authentication)
{
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
#Component
public class JwtTokenAuthorizationOncePerRequestFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private UserDetailsService JwtLdapUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.http.request.header}")
private String tokenHeader;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
logger.debug("Authentication Request For '{}'", request.getRequestURL());
final String requestTokenHeader = request.getHeader(this.tokenHeader);
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
logger.error("JWT_TOKEN_UNABLE_TO_GET_USERNAME", e);
} catch (ExpiredJwtException e) {
logger.warn("JWT_TOKEN_EXPIRED", e);
}
} else {
logger.warn("JWT_TOKEN_DOES_NOT_START_WITH_BEARER_STRING");
}
logger.debug("JWT_TOKEN_USERNAME_VALUE '{}'", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.JwtLdapUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
Above are some code files (modified for ldap) in the application. Complete code (base of my changes - InMemory based auth+Jwt) is available in the link mentioned above.
Thanks,

Add additional user requirements in spring security login and handle various exceptions

I am new to Spring security, I have implemented a basic user login functionality for my app using JWT. Aside from checking for username and password at login I would like to add other parameters such as a "account is verified" boolean condition but I am not sure where to add this requirement. Additionally, I need to return a 403 forbidden response status message if the "account is verified" condition is false and return a different response status message if the username password combination isn't found at all. Here Is the code I currently have which correctly handles the login of an existing user (without checking for the "account is verified" condition) and always throws a 401 when the user is found. Any feedback would be helpful.
WebSecurityConfigurerAdapter
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final ApplicationUserDetailsService applicationUserDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig(ApplicationUserDetailsService userDetailsService) {
this.applicationUserDetailsService = userDetailsService;
this.bCryptPasswordEncoder = new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilter(new AuthenticationFilter(authenticationManager()))
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder encoder() {
return this.bCryptPasswordEncoder;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(applicationUserDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
}
UserDetailsService
public class ApplicationUserDetailsService implements UserDetailsService {
private final ApplicationUserRepository applicationUserRepository;
public ApplicationUserDetailsService(ApplicationUserRepository applicationUserRepository) {
this.applicationUserRepository = applicationUserRepository;
}
#Override
public UserDetails loadUserByUsername(String nickname)
throws UsernameNotFoundException, UserIsNotActiveException {
Optional<ApplicationUser> applicationUser =
applicationUserRepository.findByNickname(nickname);
if (!applicationUser.isPresent()) {
throw new UsernameNotFoundException(nickname);
}
return new User(
applicationUser.get().getNickname(),
applicationUser.get().getPassword(),
emptyList());
}
}
AuthenticationFilter
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
ApplicationUser applicationUser =
new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
applicationUser.getNickname(),
applicationUser.getPassword(),
new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) {
Date exp = new Date(System.currentTimeMillis() + EXPIRATION_TIME);
Key key = Keys.hmacShaKeyFor(KEY.getBytes());
Claims claims = Jwts.claims().setSubject(((User) auth.getPrincipal()).getUsername());
String token =
Jwts.builder()
.setClaims(claims)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(exp)
.compact();
res.addHeader("token", token);
}
}
AuthorizationFilter
public AuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader(HEADER_NAME);
if (header == null) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = authenticate(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken authenticate(HttpServletRequest request) {
String token = request.getHeader(HEADER_NAME);
if (token != null) {
Jws<Claims> user =
Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(KEY.getBytes()))
.build()
.parseClaimsJws(token);
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
} else {
return null;
}
}
return null;
}
ApplicationUser
public class ApplicationUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(unique = true)
String email;
#Column(unique = true)
String nickname;
String biography;
String password; // Hashed
#Builder.Default boolean isActive = false;
}
The interface UserDetails (that is returned by the UserDetailsService) has some utility methods that can help you with it.
While the account is not activated, you can return false from the UserDetails#isEnabled method, or maybe you can use UserDetails#isAccountNonLocked as well.
Those methods will then be automatically validated on the AbstractUserDetailsAuthenticationProvider$Default(Pre/Post)AuthenticationChecks class.
After the user goes through the activation flow, you can change the property to true and it will allow the user to authenticate.
Tip: add the logging.level.org.springframework.security=TRACE to your application.properties to help to debug.

Getting null token in Jwt token filter when a request is sent in?

Just started using Jwt tokens when securing my microservices and keep getting a null token in my JwtTokenFilter class when a request is sent in but don't know where from, and finding it hard to understand why?
JwtTokenFilter.class
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtTokenProvider.resolveToken(request);
System.out.println("Token: " + token);
try {
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
SecurityContextHolder.clearContext();
response.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}
filterChain.doFilter(request, response);
}
SecurityConfig.class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.antMatchers("/auth/register").permitAll()
.antMatchers("/auth/{username}").permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}
JwtTokenProvider.class
#Component
public class JwtTokenProvider {
#Value("$security.jwt.token.secret-key")
private String secretKey;
private long validityInMilliseconds = 3600000;
#Autowired
private CustomUserDetails customUserDetails;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String username, List<Role> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).collect(Collectors.toList()));
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = customUserDetails.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
If you need any more classes shown just ask, thanks for the help.
Realised the problem was how I registered it with my Gateway microservice in the spring configuration. All sorted thanks for the help.

Spring Boot and Spring Security filter not filtering correct request

I have a spring boot and spring security service.
I have extended WebSecurityConfigurerAdapter class and overridden configure method. But somehow it is not filtering correct request.
My url is something like -
localhost:8080/album/private/v1/getAlbumsByVendorId?vendorId=1
localhost:8080/vendor/private/v1/getVendor?vendorId=1
and also I have some URL which I do not want to authenticate.like below url.
localhost:8080/category/v1/getCategory
Only want to authenticate if the URL contains private.
But seems like my filter is getting invoked for all request.
is there something wrong in .antMatchers("/**/private/**")
Note - I don't have any context path as of now.
Added the classes.
Controller is just a dummy test controller.
#Configuration
#EnableWebSecurity
//#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.cors().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.antMatchers("/**/private/**").authenticated()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);
}
}
#Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
//Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
#RestController
#CrossOrigin()
public class HelloWorldController {
#RequestMapping({ "/hello" })
public String hello() {
return "Hello World";
}
#RequestMapping({ "/private/test" })
public String hello2() {
return "Hello World-test";
}
#RequestMapping({ "/v1/private/test" })
public String hello3() {
return "Hello World-test-v1";
}
#RequestMapping({ "/v1/public/test" })
public String hello4() {
return "Hello World-test-v1-public";
}
}
By default, Spring Boot will secure all endpoints when Spring Security is on the classpath. We need to explicitly add an exclusion for all other endpoints to be permitted without authentication. Consider change is .anyRequest().permitAll(),
which means each request other than /**/private/** will be accessible to everyone. In other words, the filter will only apply to /**/private/**
Git Link
approach 1 (clean way)
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.antMatchers("/**/private/**").authenticated()
.anyRequest().permitAll()
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);
}
approach 2: only check for a token if Request comes from /private/ (not an ideal way)
JwtAuthenticationEntryPoint.java
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
System.out.println("Entry Request: "+request.getRequestURI());
System.out.println("Entry Contain: "+request.getRequestURI().contains("private"));
if(request.getRequestURI().contains("private")==true)
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
JwtRequestFilter.java
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
System.out.println("JWT Request: "+request.getRequestURI());
System.out.println("JWT Contain: "+request.getRequestURI().contains("private"));
String username = null;
String jwtToken = null;
//Remove comment for second approach
if(request.getRequestURI().contains("private")==false)
{
System.out.println("Do Noting, Permit It");
chain.doFilter(request, response);
}
else if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ") ) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
http://localhost:8080/v1/private/test **401**
http://localhost:8080/v1/public/test **200**

Configuring security in a Spring Boot application

I'm upgrading an application to Spring Boot 2.0.3.
But my login request is unauthorized:
curl -H "Accept:application/json" -H "Content-Type: application/json" "http://localhost:8080/api/users/login" -X POST -d "{ \"email\" : \"myemail#somedomain.com\", \"password\" : \"xxxxx\" }" -i
The response is a 401 Unauthorized access. You failed to authenticate.
It is given by my custom entry point:
#Component
public final class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
private static Logger logger = LoggerFactory.getLogger(RESTAuthenticationEntryPoint.class);
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
logger.debug("Security - RESTAuthenticationEntryPoint - Entry point 401");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized access. You failed to authenticate.");
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("User REST");
super.afterPropertiesSet();
}
}
The debugger shows the authenticate method of my CustomAuthenticationProvider is not called as I expect it to be:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
CredentialsService credentialsService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String email = authentication.getName();
String password = authentication.getCredentials().toString();
List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<SimpleGrantedAuthority>();
User user = null;
try {
user = credentialsService.findByEmail(new EmailAddress(email));
} catch (IllegalArgumentException e) {
throw new BadCredentialsException("The login " + email + " and password could not match.");
}
if (user != null) {
if (credentialsService.checkPassword(user, password)) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new UsernamePasswordAuthenticationToken(email, password, grantedAuthorities);
} else {
throw new BadCredentialsException("The login " + user.getEmail() + " and password could not match.");
}
}
throw new BadCredentialsException("The login " + authentication.getPrincipal() + " and password could not match.");
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
But the filter is exercised and a null token is found:
#Component
public class AuthenticationFromTokenFilter extends OncePerRequestFilter {
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
tokenAuthenticationService.authenticateFromToken(request);
chain.doFilter(request, response);
}
}
#Service
public class TokenAuthenticationServiceImpl implements TokenAuthenticationService {
private static Logger logger = LoggerFactory.getLogger(TokenAuthenticationServiceImpl.class);
private static final long ONE_WEEK = 1000 * 60 * 60 * 24 * 7;
private static final String TOKEN_URL_PARAM_NAME = "token";
#Autowired
private ApplicationProperties applicationProperties;
#Autowired
private UserDetailsService userDetailsService;
public void addTokenToResponseHeader(HttpHeaders headers, String username) {
String token = buildToken(username);
headers.add(CommonConstants.AUTH_HEADER_NAME, token);
}
public void addTokenToResponseHeader(HttpServletResponse response, Authentication authentication) {
String username = authentication.getName();
if (username != null) {
String token = buildToken(username);
response.addHeader(CommonConstants.AUTH_HEADER_NAME, token);
}
}
private String buildToken(String username) {
String token = null;
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null) {
Date expirationDate = new Date(System.currentTimeMillis() + ONE_WEEK);
token = CommonConstants.AUTH_BEARER + " " + Jwts.builder().signWith(HS256, getEncodedPrivateKey()).setExpiration(expirationDate).setSubject(userDetails.getUsername()).compact();
}
return token;
}
public Authentication authenticateFromToken(HttpServletRequest request) {
String token = extractAuthTokenFromRequest(request);
logger.debug("The request contained the JWT token: " + token);
if (token != null && !token.isEmpty()) {
try {
String username = Jwts.parser().setSigningKey(getEncodedPrivateKey()).parseClaimsJws(token).getBody().getSubject();
if (username != null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
logger.debug("Security - The filter authenticated fine from the JWT token");
}
} catch (SignatureException e) {
logger.info("The JWT token " + token + " could not be parsed.");
}
}
return null;
}
private String extractAuthTokenFromRequest(HttpServletRequest request) {
String token = null;
String header = request.getHeader(CommonConstants.AUTH_HEADER_NAME);
if (header != null && header.contains(CommonConstants.AUTH_BEARER)) {
int start = (CommonConstants.AUTH_BEARER + " ").length();
if (header.length() > start) {
token = header.substring(start - 1);
}
} else {
// The token may be set as an HTTP parameter in case the client could not set it as an HTTP header
token = request.getParameter(TOKEN_URL_PARAM_NAME);
}
return token;
}
private String getEncodedPrivateKey() {
String privateKey = applicationProperties.getAuthenticationTokenPrivateKey();
return Base64.getEncoder().encodeToString(privateKey.getBytes());
}
}
My security configuration is:
#Configuration
#EnableWebSecurity
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.user.rest.security", "com.thalasoft.user.rest.filter" })
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationFromTokenFilter authenticationFromTokenFilter;
#Autowired
private SimpleCORSFilter simpleCORSFilter;
#Autowired
private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.authenticationProvider(new CustomAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(simpleCORSFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class)
.headers().cacheControl().disable().frameOptions().disable()
.and()
.userDetailsService(userDetailsService)
.authorizeRequests()
.antMatchers(RESTConstants.SLASH + UserDomainConstants.USERS + RESTConstants.SLASH + UserDomainConstants.LOGIN).permitAll()
.antMatchers(RESTConstants.SLASH + RESTConstants.ERROR).permitAll()
.antMatchers("/**").hasRole(UserDomainConstants.ROLE_ADMIN).anyRequest().authenticated();
}
}
The user details service is:
#Component
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private CredentialsService credentialsService;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (username != null && !username.isEmpty()) {
User user = credentialsService.findByEmail(new EmailAddress(username));
if (user != null) {
return new UserDetailsWrapper(user);
}
}
throw new UsernameNotFoundException("The user " + username + " was not found.");
}
}
Why is the custom authentication provider not authenticating the username and password ?
UPDATE:
I read something interesting and puzzling in this guide
Note that the AuthenticationManagerBuilder is #Autowired into a method in a #Bean - that is what makes it build the global (parent) AuthenticationManager. In contrast if we had done it this way (using an #Override of a method in the configurer) then the AuthenticationManagerBuilder is only used to build a "local" AuthenticationManager, which is a child of the global one. In a Spring Boot application you can #Autowired the global one into another bean, but you can’t do that with the local one unless you explicitly expose it yourself.
So, is there anything wrong with my usage of the configure method for setting up the authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider); ?
Instead of the above configuration, I tried the following configuration:
#Autowired
public void initialize(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider);
}
But it still didn't exercise the custom authentication provider upon a request.
I also tried to have the filter after as in:
http.addFilterAfter(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class);
instead of addFilterBefore but it didn't change anything to the issue.
In WebSecurityConfiguration inside configure(HttpSecurity http) method:
http.authorizeRequests().antMatchers("/api/users/login").permitAll();
http.authorizeRequests().anyRequest().authenticated();
Add in the same order.
Explanation: Login and logout requests should be permitted without any authentication
A sample configure method that works is:
http.formLogin().disable().logout().disable().httpBasic().disable();
http.authorizeRequests().antMatchers("/logout", "/login", "/").permitAll();
http.authorizeRequests().anyRequest().authenticated();
http.addFilterBefore(new SomeFilter(), SecurityContextHolderAwareRequestFilter.class);
http.addFilterBefore(new CORSFilter(env), ChannelProcessingFilter.class);
http.addFilterBefore(new XSSFilter(),CORSFilter.class);
According to me when we implement our own ApplicationFilter by implementing GenericFilterBean we need to check if the token received from the request is valid or not. If it is not valid then we need to dump the token into the security context (for the authentication-provider to pick up). I haven't gone through your filter class. But this worked for me :
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httprequset=(HttpServletRequest)request;
String uname=request.getParameter("username");
String pwd=request.getParameter("password");
String role=request.getParameter("role");
List<GrantedAuthority> l = new ArrayList<>();
l.add( new SimpleGrantedAuthority(role.toUpperCase()) );
UsernamePasswordAuthenticationToken token=new UsernamePasswordAuthenticationToken(uname,pwd,l);
token.setAuthenticated(false);
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(httprequset, response);
}

Resources