I have implemented SpringSecurity with JWT, all working nicely except one thing
#PreAuthorize("hasAuthority('admin:read')") isnt working, it gives
403 FORBIDDEN error
I will share parts of important codes. The token return all authorities properly which I added as SimpleGrantedAuthority
SecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...logic
}
AccountController
#RestController
#RequestMapping("/api")
public class AccountController {
//this works
#RequestMapping(value = "/accounts", method = RequestMethod.GET)
#PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public ResponseEntity<?> loadAll() {
List<AccountDTO> res = accountService.loadAll();
return new ResponseEntity(new ResponseWrapper(res), HttpStatus.OK);
}
//this doesn't work
#RequestMapping(value = "/accounts/{uid}", method = RequestMethod.GET)
#PreAuthorize("hasAuthority('admin:read')")
ResponseEntity<?> findByUId(#PathVariable String uid) throws Exception {
AccountDTO dto = this.accountService.findByUid(uid);
return new ResponseEntity<>(new ResponseWrapper(dto), HttpStatus.OK);
}
}
Token
{
"sub": "nenad.arbutina",
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "admin:read"
},
{
"authority": "admin:create"
},
{
"authority": "admin:update"
},
{
"authority": "admin:delete"
}
],
"iat": 1631111042,
"exp": 1632261600
}
update on code. When debugged I am getting only ROLE_ADMIN, so I am confused
SecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailService myUserDetailService;
private final SecretKey secretKey;
private final JwtConfig jwtConfig;
#Autowired
public SecurityConfig(
MyUserDetailService myUserDetailService,
SecretKey secretKey,
JwtConfig jwtConfig) {
this.myUserDetailService = myUserDetailService;
this.secretKey = secretKey;
this.jwtConfig = jwtConfig;
}
#Bean
PasswordEncoder getEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, secretKey))
.addFilterAfter(new JwtTokenVerifier(secretKey, jwtConfig), JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "index","/css/*", "/js/*").permitAll()
.anyRequest()
.authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new
DaoAuthenticationProvider();
provider.setPasswordEncoder(getEncoder());
provider.setUserDetailsService(myUserDetailService);
return provider;
}
}
JwtTokenVerifier
public class JwtTokenVerifier extends OncePerRequestFilter {
private final SecretKey secretKey;
private final JwtConfig jwtConfig;
public JwtTokenVerifier(SecretKey secretKey,
JwtConfig jwtConfig) {
this.secretKey = secretKey;
this.jwtConfig = jwtConfig;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader(jwtConfig.getAuthorizationHeader());
if (Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) {
filterChain.doFilter(request, response);
return;
}
String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");
try {
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
Claims body = claimsJws.getBody();
String username = body.getSubject();
List<Map<String, String>> authorities = (List<Map<String, String>>) body.get("authorities");
Set<SimpleGrantedAuthority> simpleGrantedAuthorities = authorities.stream()
.map(m -> new SimpleGrantedAuthority(m.get("authority")))
.collect(Collectors.toSet());
Authentication authentication = new UsernamePasswordAuthenticationToken(
username,
null,
simpleGrantedAuthorities
);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (JwtException e ){
throw new IllegalStateException(String.format("Token %s is not to be trusted", token));
}
}
}
Related
I have a RestController class with a method that returns duplicate json. Weird thing is when I pass a string to the response object. The Json is okay. However when I pass any other object, be it a list, hashmap etc. I get the json duplicated. Am checking the response in PostMan.
Sample Code sending correct response with string.
#RequestMapping(value = "test", method = RequestMethod.GET)
public ResponseEntity<?> test() {
return ResponseEntity
.ok().
body("My test string");
}
Output
Code sending duplicate Json with HashMap or any Model
#RequestMapping(value = "test", method = RequestMethod.GET)
public ResponseEntity<?> test() {
HashMap<String, Object> fullObject = new HashMap<String, Object>();
return ResponseEntity
.ok().
body(fullObject);
}
The response I get
Here is my security config where I pass a few filters during authentication.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtConfig jwtConfig;
private final PasswordEncoder passwordEncoder;
private final ApplicationUserService applicationUserService;
#Autowired
public ApplicationSecurityConfig(PasswordEncoder passwordEncoder, ApplicationUserService applicationUserService, JwtConfig jwtConfig) {
this.applicationUserService = applicationUserService;
this.passwordEncoder = passwordEncoder;
this.jwtConfig = jwtConfig;
}
//JWT AUTHENTICATION
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(),jwtConfig))
.addFilterAfter(new JwtTokenVerifier(jwtConfig), JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "index", "/css/*", "/js/*", "/h2/**","/login", "/swagger-ui/**", "/v3/api-docs/**", "/token/refresh").permitAll()
.antMatchers("/api/**").hasRole(STUDENT.name())
.antMatchers(HttpMethod.DELETE, "/management/api/**").hasAuthority(COURSE_WRITE.getPermission())
.antMatchers(HttpMethod.POST, "/management/api/**").hasAuthority(COURSE_WRITE.getPermission())
.antMatchers(HttpMethod.PUT, "/management/api/**").hasAuthority(COURSE_WRITE.getPermission())
.antMatchers("/management/api/**").hasAnyRole(ADMIN.name(), ADMINTRAINEE.name())
.anyRequest()
.authenticated();
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(applicationUserService);
return provider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
}
My JWTTokenVerifier
public class JwtTokenVerifier extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
public JwtTokenVerifier(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getServletPath().equals("/login") || request.getServletPath().equals("/token/refresh")) {
filterChain.doFilter(request, response);
}
String authorizationHeader = request.getHeader(jwtConfig.getAuthorizationHeader());
if (Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) {
filterChain.doFilter(request, response);
return;
}
String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");
try {
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("authorities").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role));
});
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (IllegalStateException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", token));
}
filterChain.doFilter(request, response);
}
}
My JWTUsernameandpassword
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JwtConfig jwtConfig;
public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager,
JwtConfig jwtConfig) {
this.authenticationManager = authenticationManager;
this.jwtConfig = jwtConfig;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
);
Authentication authenticate = authenticationManager.authenticate(authentication);
return authenticate;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
/* String access_token = Jwts.builder()
.setSubject(authResult.getName())
.claim("authorities", authResult.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusDays(jwtConfig.getTokenExpirationAfterDays())))
.signWith(secretKey)
.compact();
String refresh_token = Jwts.builder()
.setSubject(authResult.getName())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusDays(jwtConfig.getRefreshTokenExpirationAfterDays())))
.signWith(secretKey)
.compact();*/
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
String access_token = JWT.create()
.withSubject(authResult.getName())
.withExpiresAt(new Date(System.currentTimeMillis() + 10 * 6000 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim("authorities", authResult.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.sign(algorithm);
String refresh_token = JWT.create()
.withSubject(authResult.getName())
.withExpiresAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.sign(algorithm);
// response.addHeader(jwtConfig.getAuthorizationHeader(), jwtConfig.getTokenPrefix() + access_token);
// response.setHeader("jwt_access", jwtConfig.getTokenPrefix() + access_token);
// response.setHeader("jwt_refresh", jwtConfig.getTokenPrefix() + refresh_token);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", jwtConfig.getTokenPrefix() + access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE); //"application/json"
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
}
}
In your JwtTokenVerifier you call filterChain.doFilter(request, response) multiple times. For example, you call it both in the try/catch and outside:
try {
// ...
// Called here
filterChain.doFilter(request, response);
} catch (IllegalStateException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", token));
}
// And here
filterChain.doFilter(request, response);
The solution is to refactor your code so that it's only calling the filterChain.doFilter(request, response) statement once.
P.S. You also call filterChain.doFilter(request, response) in the if-statements at the top of your logic. So for the /login or /token/refresh endpoint, the response might be tripled.
I have endpoints that will be called only from authenticated users. I have valid JWT token that I am passing as a Bearer Token in Authorization. When I do get request to http://localhost:8080/api/students/all, I am getting 401:
The logger logs in the console Authenticated user test#gmail.com[ROLE_STUDENT], setting security context, which means that the token is successfully validated. Why am I still gettin 401? How can I solve this issue?
Here is my StudentController.java:
#RestController
#CrossOrigin(origins = "http://localhost:3000")
#Slf4j
#RequestMapping("/api/students")
public class StudentController {
#Autowired
private StudentService studentService;
#GetMapping("/all")
public ResponseEntity<List<Student>> getAllStudents(){
log.info("test");
return new ResponseEntity<List<Student>>(studentService.getAll(), HttpStatus.OK);
}
}
JwtTokenUtil class is:
#Component
public class JwtTokenUtil implements Serializable{
#Value("${jwt.public.key}")
private String publicKey;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(publicKey)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(User user, String scope) {
return doGenerateToken(user.getEmail(), scope);
}
private String doGenerateToken(String subject, String scope) {
Claims claims = Jwts.claims().setSubject(subject);
claims.put("roles",scope);
return Jwts.builder()
.setClaims(claims)
.setIssuer("http://uni.pu.com")
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 5*60*60*1000))
.signWith(SignatureAlgorithm.HS256, publicKey)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (
username.equals(userDetails.getUsername())
&& !isTokenExpired(token));
}
}
JWTAuthenticationFilter class:
public class JWTAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private MyUserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
if (header != null && header.startsWith(TOKEN_PREFIX)) {
authToken = header.replace(TOKEN_PREFIX, "");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
logger.error("an error occured during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.warn("the token is expired and not valid anymore", e);
} catch (SignatureException e) {
logger.error("Authentication Failed. Username or Password not valid.");
}
} else {
logger.warn("couldn't find bearer string, will ignore the header");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
logger.info("authenticated user " + username + userDetails.getAuthorities()+ ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(req, res);
}
}
WebSecurityConfig:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)
public class WebSecurityConfig {
private static final String[] WHITE_LIST_URLS = { "/", "/api/students/register", "/api/students/register/*",
"/api/user/reset-password", "/api/user/save-password", "/api/user/login", "/api/user/logout",
// -- Swagger UI v2
"/v2/api-docs", "/swagger-resources", "/swagger-resources/**", "/configuration/ui", "/configuration/**",
"/configuration/security", "/swagger-ui.html", "/webjars/**",
// -- Swagger UI v3 (OpenAPI)
"/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui/", "/swagger-ui" };
#Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
#Value("${jwt.public.key}")
private RSAPublicKey rsaPublicKey;
#Value("${jwt.private.key}")
private RSAPrivateKey rsaPrivateKey;
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(10);
}
#Bean
public JWTAuthenticationFilter authenticationTokenFilterBean() throws Exception {
return new JWTAuthenticationFilter();
}
#Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Enable CORS and disable CSRF
http = http.cors().and().csrf().disable();
// Set session management to stateless
http = http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and();
// Set unauthorized requests exception handler
http = http.exceptionHandling(
(exceptions) -> exceptions.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(new BearerTokenAccessDeniedHandler()));
http = http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http = http.formLogin().disable();
// Set permissions on endpoints
http.authorizeHttpRequests().antMatchers(WHITE_LIST_URLS).permitAll()
// private endpoints
.anyRequest().authenticated()
// Set up oauth2 resource server
.and().httpBasic(Customizer.withDefaults()).oauth2ResourceServer().jwt();
return http.build();
}
#Bean
public JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(this.rsaPublicKey).privateKey(this.rsaPrivateKey).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
// Used by JwtAuthenticationProvider to decode and validate JWT tokens
#Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.rsaPublicKey).build();
}
// Extract authorities from the roles claim
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_ADMIN > ROLE_INSPECTOR \n ROLE_INSPECTOR > ROLE_STUDENT";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
}
MyUserDetailsService:
#Service
#Transactional
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
public UserDetails loadUserByUsername(String email) throws UserNotFoundException {
Optional<User> optionalUser = userRepository.findByEmail(email);
if(!optionalUser.isPresent()) {
throw new UserNotFoundException("No user found with email: " + email);
}
User user = optionalUser.get();
boolean enabled = user.isEnabled();
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(),
enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, getAuthorities(user.getRoles()));
}
public Collection<? extends GrantedAuthority> getAuthorities(Collection<Role> roles) {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
}
I have implemented a custom exception handler that handles all the exception that I am throwing:
#ControllerAdvice
public class AppExceptionHandler {
#ExceptionHandler(value = {UserServiceException.class})
public ResponseEntity<Object>handleUserServiceException(
UserServiceException ex, WebRequest request){
ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage());
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR );
}
//A universal exception handling method
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<Object>handleUserOtherExceptions(Exception ex, WebRequest request){
ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage());
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR );
}
}
The Universal exception handler method doesn't gets called for 403 status response, the response body is empty but I want to see a response body.
Below is my Security class:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter{
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
// #Autowired
// AuthenticationFailureHandler authenticationFailureHandler;
public WebSecurity(UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL)
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilter(getAuthenticationFilter())
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(bCryptPasswordEncoder);
}
public AuthenticationFilter getAuthenticationFilter() throws Exception{
final AuthenticationFilter filter = new AuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl("/users/login");
return filter;
}
Below is my AuthenticationFilter:
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter{
private final AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
UserLoginRequestModel creds = new ObjectMapper()
.readValue(req.getInputStream(), UserLoginRequestModel.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(), creds.getPassword(), new ArrayList<>()));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req
, HttpServletResponse res
, FilterChain chain
, Authentication auth)
throws IOException, ServletException {
String userName = ((User) auth.getPrincipal()).getUsername();
String token = Jwts.builder()
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.getTokenSecret())
.compact();
UserService userService = (UserService) SpringApplicationContext.getBean("userServiceImpl");
UserDto userDto = userService.getUser(userName);
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
res.addHeader(SecurityConstants.USER_ID, userDto.getUserId());
}
}
Below is my AuthorizationFilter:
public class AuthorizationFilter extends BasicAuthenticationFilter {
public AuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader(SecurityConstants.HEADER_STRING);
if(header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(SecurityConstants.HEADER_STRING);
if(token != null) {
token = token.replace(SecurityConstants.TOKEN_PREFIX, "");
String user = Jwts.parser()
.setSigningKey(SecurityConstants.getTokenSecret())
.parseClaimsJws(token)
.getBody()
.getSubject();
if(user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
}
return null;
}
}
Below is my postman response (As you can see the body is empty
I want Spring to handle all other exceptions by calling the overriding #ExceptionHandler I have written in the above AppExceptionHandler class.
As you can see the response body is empty. How do I return the default or custom object for response body?
Im not quite sure if this is what you are looking for, but If you want to somehow customize the messages from failed authentication have a look on AuthenticationEntryPoint like in https://www.baeldung.com/spring-security-basic-authentication. There you can react on the thrown exception and customize what message and codes to return.
I am new to JWT. I create my own microservice and want to introduce JWT authentication. I have one website that issues a token and in the other I want to check the correctness of this token. I want to do this without connecting the second site to db. This approach seems to me appropriate and best for user data.
I have following payload of token:
{
"sub": "Marek",
"auth": [
{
"authority": "ROLE_USER"
}
],
"iat": 1574091010,
"exp": 1574091210
}
Its my code:
WebSecurityConfig
#Autowired
private JwtTockenCreator jwtTockenCreator;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
.antMatchers("/user/login").permitAll()
.antMatchers("/user/addUser").permitAll()
.anyRequest()
.authenticated();
http.exceptionHandling().accessDeniedPage("/login");
http.apply(new JWTConfigurer(jwtTockenCreator));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
JwtTokenFilter
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTockenCreator jwtTockenCreator;
public JwtTokenFilter(JwtTockenCreator jwtTockenCreator) {
this.jwtTockenCreator = jwtTockenCreator;
}
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
String token = jwtTockenCreator.resolveToken(httpServletRequest);
try {
if (token != null && jwtTockenCreator.validateToken(token)) {
Authentication auth = jwtTockenCreator.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
// this is very important, since it guarantees the user is not authenticated at
// all
SecurityContextHolder.clearContext();
httpServletResponse.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
JwtTockenCreator
public class JwtTockenCreator {
#Value("${security.secretKey}")
private String secretKey;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(secretKey).toString().split(",")).stream()
.map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken( principal,"",authorities);
}
public String getUsernameFromToken(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);
}
}
}
JWTConfigurer
public class JWTConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private JwtTockenCreator jwtTockenCreator;
public JWTConfigurer(JwtTockenCreator jwtTockenCreator) {
this.jwtTockenCreator = jwtTockenCreator;
}
#Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(jwtTockenCreator);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
UserController
#CrossOrigin
#RestController
#RequestMapping("/user")
public class UserController {
#Autowired
RestTemplate restTemplate;
#Value("${hostname}")
public String hostname;
#Value("${user.port}")
public String userPort;
#PostMapping("/login")
public ResponseEntity<String> login(#RequestBody User user) {
String urlUser = hostname + userPort + "/user/login";
String token = restTemplate.postForObject(urlUser, user, String.class);
return ResponseEntity.ok(token);
}
#PreAuthorize("hasRole('USER')")
#PostMapping("/addUser")
public ResponseEntity<String> registerAction(#RequestBody User user) {
String urlUser = hostname + userPort + "/user/addUser";
String token = restTemplate.postForObject(urlUser, user, String.class);
return ResponseEntity.ok(token);
}
}
In Eclipse doesn't give any errors. That's why I don't know what I'm doing wrong
when I want to call / user / addUser and add a new user nothing happens. In the User service I call, I have a function responsible for adding users and it works correctly when I refer to it directly. And if I want to do it through my Rest Api it doesn't work anymore. And it is my problem that I do not know what can happen because I have no mistake. I remind you that I am still learning and I am asking for understanding
I'm developing spring boot app with JWT. When I try permit all to 2 endpoints it isnt working. All api is secured and requires token.
Please help me write configuration. Here is my code:
Security config:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#Configuration
public class AdapterJWTSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Override
protected void configure(AuthenticationManagerBuilder auth) {
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.cors()
.and()
.authorizeRequests()
.antMatchers("/user/signIn", "/user/addUser").permitAll()
.and()
.anonymous()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.addFilter(new JWTAuthorizationFilter(authenticationManagerBean()));
}
}
I tried to do this in 2 spring security configurations but it didnt work.
JwtFilter:
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String token = req.getHeader(JwtConstants.HEADER_STRING);
if (token == null) {
try {
chain.doFilter(req, res);
} catch (JwtException e) {
System.out.println(e.getMessage());
}
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req, res);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
String token = request.getHeader(JwtConstants.HEADER_STRING);
if (token != null) {
Claims claims = validateToken(token);
if (claims != null) {
return new UsernamePasswordAuthenticationToken(claims, null, new ArrayList<>());
}
return null;
}
return null;
}
private Claims validateToken(String token) {
Claims claims = null;
try {
return Jwts.parser()
.setSigningKey(JwtConstants.SECRET)
.parseClaimsJws(token).getBody();
} catch (Exception e) {
return null;
}
}
}
Controller
If it could be helpful
#CrossOrigin(origins = "*")
#RestController
#RequestMapping("/rest/user/")
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#PostMapping("addUser")
public List<String> addUser(#Valid #RequestBody User user,BindingResult bindingResult) {
return userService.addNewUser(user, bindingResult);
}
#PostMapping("signIn")
public List<String> generate(#Valid #RequestBody UserRequestLogin user) {
return userService.signIn(user);
}
}