How to call controller method after authentication - java

I have following metod in controller:
#PostMapping(path = "/api/users/login", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseStatus(OK)
public TokenResponse login(#RequestBody LoginUserRequest loginUserRequest, Principal principal) {
return new TokenResponse().setAccessToken("token");
}
here is a WebSecurityConfigurerAdapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().disable()
.authorizeRequests().antMatchers("/api/users/login").permitAll()
.and()
.authorizeRequests().antMatchers("/api/**").authenticated()
.and()
.addFilterBefore(mobileAuthenticationFilter(objectMapper), UsernamePasswordAuthenticationFilter.class)
.addFilter(new JwtAuthorizationFilter(authenticationManager(), super.userDetailsService()));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("SELECT login, pass, active FROM users WHERE login = ?")
.authoritiesByUsernameQuery("SELECT login, 'ROLE_USER' FROM users WHERE login = ?")
.passwordEncoder(new CustomPasswordEncoder());
}
#Bean
public MobileAuthenticationFilter mobileAuthenticationFilter(ObjectMapper objectMapper) throws Exception {
MobileAuthenticationFilter mobileAuthenticationFilter = new MobileAuthenticationFilter(objectMapper);
mobileAuthenticationFilter.setAuthenticationManager(authenticationManager());
mobileAuthenticationFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
System.out.println(request);
});
return mobileAuthenticationFilter;
}
MobileAuthenticationFilter is reading from json body and prepare UsernamePasswordAuthenticationToken
public class MobileAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final ObjectMapper objectMapper;
public MobileAuthenticationFilter(ObjectMapper objectMapper) {
super(new AntPathRequestMatcher("/api/users/login"));
this.objectMapper = objectMapper;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
BufferedReader reader = request.getReader();
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.mark(0);
LoginUserRequest loginUserRequest = objectMapper.readValue(sb.toString(), LoginUserRequest.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginUserRequest.getLogin(), loginUserRequest.getPassword());
return getAuthenticationManager().authenticate(token);
} catch (IOException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
}
this code works fine but is one thing which I want to archive.
After successfully authentication, response is produced immediately by the:
mobileAuthenticationFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
System.out.println(request);
});
Here ofcourse I can return something to client (in body), but there is any possibility to invoke controller method public TokenResponse login and that method should return a response (based on method contract and annotations for http code)?
This method in controller in that scenario is never called.

Would there be a formLogin, you could have used the successHandler(...) to redirect to the page you want. Note that you have to also think about error responses.
Since you have explicitly disabled formLogin, I recommend if users call /api/users/login instead of authenticating them in attemptAuthentication(...).
So, as you have put it ..addFilterBefore(mobileAuthenticationFilter(objectMapper), UsernamePasswordAuthenticationFilter.class), your filter will be triggered populating the resulting response.
Your controller will look like something like this:
public TokenResponse login(#Valid #RequestBody LoginUserRequest loginUserRequest) {
//may be check for AuthenticationException
try{
...
generateToken(loginUserRequest.getUserName(), loginUserRequest.getPassword());
...
} catch(AuthenticationException ex){
// status = HttpStatus.UNAUTHORIZED;
} catch (Exception ex) {
//status = HttpStatus.INTERNAL_SERVER_ERROR;
}
}
public String generateToken(String name, String password) {
try {
// check for null or empty
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(name, password, new ArrayList<>());
Authentication authentication = authenticationManager.authenticate(upToken);
// do whatever operations you need and return token
} catch (Exception ex) {
throw ex;
}
}

Related

Spring Rest Forbidden Access when hit Exception

I'm using Spring Boot 3.0. The authorization just works as expected but when it hit SomeException like MethodArgumentNotValidException it just only show 403 Forbidden Access with empty body. Before I'm using Spring Boot 3.0 everything just work as I'm expected when hitting Exception like they give me the exception JSON result.
SecurityConfiguration
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
var secret = System.getProperty("app.secret");
var authorizationFilter = new AuthorizationFilter(secret);
var authenticationFilter = new AuthenticationFilter(secret, authenticationManager);
authenticationFilter.setFilterProcessesUrl("/login");
authenticationFilter.setPostOnly(true);
return http
.cors().and()
.csrf((csrf) -> csrf.disable())
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/login/**", "/trackers/camera/**").permitAll()
.requestMatchers("/sites/**").hasAnyRole(Role.OWNER.name())
.anyRequest().authenticated()
)
.addFilter(authenticationFilter)
.addFilterBefore(authorizationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
var config = new CorsConfiguration();
var all = Arrays.asList("*");
config.setAllowedOrigins(all);
config.setAllowedHeaders(all);
config.setAllowedMethods(all);
config.setExposedHeaders(all);
var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}
AuthenticationFilter
#RequiredArgsConstructor
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final String secretToken;
private final AuthenticationManager authenticationManager;
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username, password;
try {
var requestMap = new ObjectMapper().readValue(request.getInputStream(), LoginRequest.class);
username = requestMap.getUsername();
password = requestMap.getPassword();
} catch (Exception e) {
throw new AuthenticationServiceException(e.getMessage(), e);
}
var token = new UsernamePasswordAuthenticationToken(username, password);
return authenticationManager.authenticate(token);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
var user = (UserDetails) authResult.getPrincipal();
var algorithm = Algorithm.HMAC512(secretToken.getBytes());
var token = JWT.create()
.withSubject(user.getUsername())
.withIssuer(request.getRequestURL().toString())
.withClaim("roles", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.sign(algorithm);
var jsonMap = new HashMap<String, String>();
jsonMap.put("token", token);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), jsonMap);
response.flushBuffer();
}
}
AuthorizationFilter
#RequiredArgsConstructor
public class AuthorizationFilter extends OncePerRequestFilter {
private final String secretToken;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
var authentication = request.getHeader(HttpHeaders.AUTHORIZATION);
if(authentication != null) {
if(authentication.startsWith("Bearer")) {
var token = authentication.substring("Bearer ".length());
var algorithm = Algorithm.HMAC512(secretToken.getBytes());
var verifier = JWT.require(algorithm).build();
var message = verifier.verify(token);
var subject = message.getSubject();
var roles = message.getClaim("roles").asArray(String.class);
var authorities = new ArrayList<SimpleGrantedAuthority>();
Arrays.stream(roles).forEach(role -> authorities.add(new SimpleGrantedAuthority(role)));
var authenticationToken = new UsernamePasswordAuthenticationToken(subject, token, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
} else if(authentication.startsWith("Basic")) {
var token = authentication.substring("Basic ".length());
var bundle = new String(Base64.getDecoder().decode(token)).split(":", 2);
if(bundle.length == 2 && bundle[0].equals(System.getProperty("app.camera.username")) && bundle[1].equals(System.getProperty("app.camera.password"))) {
var authenticationToken = new UsernamePasswordAuthenticationToken("camera1", null, Arrays.asList(new SimpleGrantedAuthority(Role.USER.getAuthority())));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
filterChain.doFilter(request, response);
}
}
If I understood you correctly, you want the exception-message shown in the return body of the request.
I solved this problem by implementing a (global) exception handler.
(Optional) Create a custom exception, extending some sort of other exception.
public class ApiException extends RuntimeException {
// Not really needed here, as Throwable.java has a message too
// I added it for better readability
#Getter
private String message;
public ApiException() {
super();
}
public ApiException(String message) {
this.message = message;
}
public ApiException(String message, Throwable cause) {
super(message, cause);
}
}
(Optional) A Wrapper, with custom information. (This is the object returned in the body).
// I've used a record, as the wrapper simply has to store data
public record ApiError(String message, HttpStatus status, Throwable cause, ZonedDateTime timestamp) {}
The handler
To create the handler, you simply have to create a custom class, which extends the ResponseEntityExceptionHandler.java
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
// The annotation's value can be replaced by any exception.
// Use Throwable.class to handle **all** exceptions.
// For this example I used the previously created exception.
#ExceptionHandler(value = { ApiException.class })
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleApiRequestException(ApiException e) {
// At this point, you can create the exception wrapper to create a
// formatted JSON-response, but you could also just get the info
// required from the exception and return that.
ApiError error = new ApiError(
e.getMessage(),
HttpStatus.BAD_REQUEST,
null,
ZonedDateTime.now(ZoneId.of("Z"))
);
return new ResponseEntity<>(error, error.status());
}
}
Also: To handle different kinds of exceptions differently, like e.g. you want a ApiException to return a 403 and a FooException to return a 404, just create another method inside of the handler and adjust it to your likings.
I hope this helped!
Cheers

Spring security HttpServletRequest and SecurityContextHolder logout not working

I am relatively new to Spring Security and I am attempting to create an endpoint for logging out an user. The code I have tried so far:
public ResponseEntity<String> logout(HttpServletRequest request, HttpServletResponse response) {
// Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// if (auth != null) {
// new SecurityContextLogoutHandler().logout(request, response, auth);
// System.out.println("logging out");
// return new ResponseEntity<>(HttpStatus.OK);
// }
try {
request.logout();
System.out.println("successful logout");
} catch (ServletException e) {
e.printStackTrace();
}
return new ResponseEntity<>(HttpStatus.OK);
}
My UserDetailsServiceImpl:
#Service
public class AccountDetailsServiceImpl implements UserDetailsService {
private final AccountRepository accountRepository;
public AccountDetailsServiceImpl(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountRepository.findByUsernameOrEmail(username, username);
if (account == null) {
throw new UsernameNotFoundException(username);
}
return new CustomUserDetails(account);
}
}
My security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, securityConstraintsProperties.getSignUpUrl()).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager(), getApplicationContext(), securityConstraintsProperties))
.addFilter(new JWTAuthorizationFilter(authenticationManager(), securityConstraintsProperties))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
I have seen both possibilities in different Stackoverflow answers, but unfortunately none of them is working for me. When I perform a request after logging out, the request is still possible. How is that possible?
Thank you in advance!

JWT token in Spring Boot configuration problem

I'm having the problem that when I call the post request: localhost:8080/authenticate
The security of my applications says it need and token. When the request is called, filters go over it, so this is not the intention. Now the security asks for a bearer token while this is the first request and that of course it is not present yet. I'm getting the error JWT Token does not begin with Bearer String
My configure method:
#Override
protected void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/authenticate");
}
#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);
}
My filter method:
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("authenticate"));
String username = null;
String jwtToken = null;
//Remove comment for second approach
if (request.getRequestURI().contains("authenticate") == 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);
}
My controller class:
#RestController
#CrossOrigin
public class JwtAuthenticationController {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private JwtUserDetailsService userDetailsService;
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> saveUser(#RequestBody UserDTO user) throws Exception {
return ResponseEntity.ok(userDetailsService.save(user));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}
I want the program work so that when I send the localhost:8080/authenticate request there will be no filters, but when I call every other request there will be the filters to check if the token is present.
Thank You in advance.
Override the method configure(WebSecurity web) to ignore /authenticate endpoint so that it will not be included in Spring Security Filter Chain as below;
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/authenticate");
}

Why Refreshing access token gets unauthorized?

As access token expires, client sends GET request with /refresh endpoint,but it ended with 401-unauthorized.
I have configured this request in webConfig. so, it does not need authorization. I have passed access token in header of /refresh request.
Note: If I don't pass token in header of /refresh request, It worked fine.
JwtAuthenticationController.java:
#RestController
public class JwtAuthenticationController {
#RequestMapping(value = "/refresh", method = RequestMethod.GET)
public ResponseEntity<?> refreshAuthenticationToken(HttpServletRequest request) {
final String token = request.getHeader("Authorization");
final String username = jwtUtils.getUsernameFromToken(token);
final UserDetails user = userDetailsService.loadUserByUsername(username);
if ((user.getUsername()).equals(username) && jwtUtils.isTokenExpired(token)) {
final String refreshedToken = jwtUtils.refreshToken(token);
return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));
}
else {
return ResponseEntity.badRequest().body(null);
}
}
}
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.exceptionHandling()
.antMatchers("/register","/refresh")
.permitAll()
.anyRequest().authenticated();
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl().disable();
}
}
JwtAuthenticationFilter.java
#Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String authToken = request.getHeader(AUTHORIZATION_HEADER);
if (authToken != null && authToken.startsWith(BEARER_PREFIX)) {
try {
authToken = authToken.substring(BEARER_PREFIX_LENGTH);
username = jwtUtils.getUsernameFromToken(authToken);
}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");
}
}
JwtUtils.java
private Claims getClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.requireIssuer(issuer)
.parseClaimsJws(token)
.getBody();
}
error log
I am unable to find out why this happen? What is the solution to get new access token using client sent refresh token.
If I don't pass token in header of /refresh request, It worked fine.
This is probably because of the JwtAuthenticationFilter. I think you should exclude also the /refresh from the check, so:
if (!request.getRequestURI().contains("/refresh") {
if (authToken != null && authToken.startsWith(BEARER_PREFIX)) {
//same logic
}
}

Why BadCredentialsException can not be thrown or handled in my customized AuthenticationProvider?

I try to use JWT to secure my resource based on Spring Security to achieve the following:
1. Token invalid or expired, return 401.
2. Successfully authorized but have no right to reach some controllers. Then return 403.
Now there is something wrong with it. I throw BadCredentialsException in my customized AuthenticationProvider (named TokenAuthenticationProvider) while user fails to be authenticated. But it finally returns 403. What can I do to handle the exception and return 403 http code.
I tried to implement AuthenticationEntryPoint but it doesn't work.
And one another way to handle the exception is using customized filter to catch the Exception. But this way definitely doesn't work because even the http response doesn't show 500 BadCredentialsException. So there must be a place already catching this Exception and I can't understand.
TokenAuthenticationProvider.class
public class TokenAuthenticationProvider implements AuthenticationProvider {
UserService userService;
public TokenAuthenticationProvider(UserService userService) {
this.userService = userService;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new BadCredentialsException("hello");
}
#Override
public boolean supports(Class<?> aClass) {
System.out.println(aClass);
TokenAuthenticationProvider.class.isAssignableFrom(aClass);
return true;
}
}
WebSecurity.class
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.addFilterAfter(new TokenAuthenticationFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().hasRole("API");
}
#Override
protected void configure(AuthenticationManagerBuilder auth){
auth.authenticationProvider(new TokenAuthenticationProvider(userService));
}
}
TokenAuthenticationFilter.class
public class TokenAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
SecurityContextHolder.getContext().setAuthentication(new TokenAuthentication("hello"));
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
The above code has been simplified. Instead of following a normal process, I directly throw the BadCredentialsException. What can I do to handle this Exception and return 401 http code.
You need to implement two filters to control the JWT generated.
First Filter is to authenticate and send the JWT to the client when the authentication is successful.
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManger) {
this.authenticationManager = authenticationManger;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
AuthenticationRequest authRequest = new ObjectMapper().readValue(request.getInputStream(),
AuthenticationRequest.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
authRequest.getUsername(), authRequest.getPassword(), new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication auth) throws IOException {
Date expirationDate = DateUtil.getDateAddDays(new Date(), 1);
String token = Jwts.builder().setIssuedAt(new Date()).setIssuer(WebSecurity.ISSUER)
.setSubject(((ClientDetails)auth.getPrincipal()).getUsername())
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, HardCodeUtil.JWT_KEY).compact();
response.addHeader(WebSecurity.HEADER_AUTHORIZATION, WebSecurity.PREFIX_JWT + token);
response.addHeader(WebSecurity.HEADER_JWT_EXPIRATION_DATE, String.valueOf(expirationDate.getTime()));
ObjectMapper mapper = new ObjectMapper();
ClientExtraParams extraParams = new ClientExtraParams((byte)1);
String body = mapper.writeValueAsString(new ClientLoginResponse(((ClientDetails)auth.getPrincipal()).getClient(),
extraParams));
response.setContentType("application/json");
response.getWriter().write(body);
response.getWriter().flush();
response.getWriter().close();
}
}
The second Filter is to validate every JWT before access to the resources:
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
private static final Logger log = Logger.getLogger(JWTAuthorizationFilter.class.getName());
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader(WebSecurity.HEADER_AUTHORIZATION);
if (header == null || !header.startsWith(WebSecurity.PREFIX_JWT)) {
chain.doFilter(req, res);
return;
}
try {
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}catch (SignatureException ex) {
log.log(Level.SEVERE, "JWT SIGNING INVALID");
}catch (MalformedJwtException ex) {
log.log(Level.SEVERE, "JWT STRUCTURE INVALID");
}catch (ExpiredJwtException ex) {
log.log(Level.SEVERE, "JWT EXPIRED");
GeneralResponse jwtInvalidResponse = new GeneralResponse(ErrorsEnum.JWT_EXPIRED);
ObjectMapper mapper = new ObjectMapper();
String body = mapper.writeValueAsString(jwtInvalidResponse);
res.setContentType("application/json");
res.getWriter().write(body);
res.getWriter().flush();
res.getWriter().close();
}catch (UnsupportedJwtException ex) {
log.log(Level.SEVERE, "JWT UNSUPPORTED");
}catch (IllegalArgumentException ex) {
log.log(Level.SEVERE, "ILLEGAL ARGUMENT JWT ENVIADO");
}
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(WebSecurity.HEADER_AUTHORIZATION);
if (token != null) {
String user = Jwts.parser()
.setSigningKey(HardCodeUtil.JWT_KEY)
.parseClaimsJws(token.replace(WebSecurity.PREFIX_JWT, ""))
.getBody()
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
}
return null;
}
}
In your Spring configuration of HttpSecurity add these filters:
.and().addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
I implemented this using this library:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

Categories