I 'm coding a back-end/APIRest, part of an personal app.
I'm trying to set an authentication by JWT, with roles : ADMIN and USER. because i want to filter the following endpoint only for role ADMIN : /users/all
But when i send my http request from Insomnia App, with localhost:7777/users/all
i have an error 403 forbidden. I don't know why.
My code repository is : here (Current branch : ImplementingJWTSecurity) .
Just bellow, this is a generated JWT by my sended request, you can check it on jwt.io :
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJrZW50MSIsInJvbGVzIjoiQURNSU4iLCJleHAiOjE2NjExNTUxNDUyOTUsImlhdCI6MTY2MDA3NTE0NTI5NX0.RGJzJVkM6bB0g6YlK6FFMzbjjTZ8qPqGf9pfHMeKHfyDV_OM9lF1w8SDzys3SC-iNdBMgXMjVP972URcISwJ_A
/CONCERNED ENDPOINT CONTROLLER/
@RestController
@RequestMapping("/users")
public class AppUserController {
@RolesAllowed("ADMIN")
@GetMapping("/all")
public ResponseEntity<List<AppUserListDto>> getAllAppUsers() {
logger.info("GET /users/all");
return new ResponseEntity<List<AppUserListDto>>(appUserServiceImpl.getAllUsers(), HttpStatus.OK);
}
}
/SECURITY CONFIGURATION/
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfiguration {
@Autowired
RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
JwtFilter jwtFilter;
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http.csrf().disable().exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/users/add").permitAll()
.antMatchers("/users/all").hasRole("ADMIN")
.anyRequest().authenticated();
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ADMIN > USER";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
@Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy());
return expressionHandler;
}
}
/MY USER DETAILS SERVICE/
@Service
public class MyUserDetailService implements UserDetailsService {
@Autowired
AppUserRepository appUserRepository;
@Autowired
RoleRepository roleRepository;
@Override
public UserDetails loadUserByUsername(String appUsername) throws UsernameNotFoundException {
AppUserEntity appUser = appUserRepository.findByAppUsername(appUsername);
if (appUser == null) {
return new org.springframework.security.core.userdetails.User(
" ", " ", true, true, true, true,
getAuthorities(Arrays.asList(
roleRepository.findByRoleName("USER"))));
}
return new org.springframework.security.core.userdetails.User(
appUser.getAppUsername(), appUser.getPassword(), true, true, true,
true, getAuthorities(appUser.getRoles()));
}
private Collection<? extends GrantedAuthority> getAuthorities(
Collection<Role> roles) {
return getGrantedAuthorities(getPrivileges(roles));
}
private List<String> getPrivileges(Collection<Role> roles) {
List<String> privileges = new ArrayList<>();
List<Privilege> collection = new ArrayList<>();
for (Role role : roles) {
privileges.add(role.getRoleName());
collection.addAll(role.getPrivileges());
}
for (Privilege item : collection) {
privileges.add(item.getName());
}
return privileges;
}
private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (String privilege : privileges) {
authorities.add(new SimpleGrantedAuthority(privilege));
}
return authorities;
}
}
/JWT UTILS/
@Component
public class JwtUtils {
@Autowired
AppUserRepository appUserRepository;
long JWT_VALIDITY = 5 * 60 * 60 * 60;
@Value("${jwt.secret}")
String secret;
private final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
public String generateToken(Authentication authentication) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles",authentication.getAuthorities().stream().map(role -> role.getAuthority()).findFirst().orElseThrow(NoSuchElementException::new));
claims.put("iat",new Date(System.currentTimeMillis()));
claims.put("exp", new Date(System.currentTimeMillis() JWT_VALIDITY * 1000));
claims.put("sub", authentication.getName());
Map<String, Object> headerJwt = new HashMap<>();
headerJwt.put("alg", "HS512");
headerJwt.put("typ", "JWT");
//TODO Later : Header, claims, jwt... will be Base64urlEncoded
return Jwts.builder()
.setHeader(headerJwt)
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
AppUserEntity appUser = appUserRepository.findByAppUsername(claims.getSubject());
logger.info("THIS IS THE SUBJECT FROM CLAIMS : {}", claims.getSubject());
Collection<? extends GrantedAuthority> authorities = getAuthorities(appUser.getRoles());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
private Collection<? extends GrantedAuthority> getAuthorities(
Collection<Role> roles) {
return getGrantedAuthorities(getPrivileges(roles));
}
private List<String> getPrivileges(Collection<Role> roles) {
List<String> privileges = new ArrayList<>();
List<Privilege> collection = new ArrayList<>();
for (Role role : roles) {
privileges.add(role.getRoleName());
collection.addAll(role.getPrivileges());
}
for (Privilege item : collection) {
privileges.add(item.getName());
}
return privileges;
}
private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (String privilege : privileges) {
authorities.add(new SimpleGrantedAuthority(privilege));
}
return authorities;
}
}
/JWT CONTROLLER/
@RestController
public class JwtController {
@Autowired
JwtUtils jwtUtils;
@Autowired
AuthenticationManagerBuilder authenticationManagerBuilder;
@PostMapping("/login")
public ResponseEntity<?> createAuthToken(@RequestBody JwtRequest jwtRequest) {
Authentication authentication = logUser(jwtRequest.getAppUsername(), jwtRequest.getPassword());
String jwt = jwtUtils.generateToken(authentication);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(AUTHORIZATION_HEADER, "Bearer " jwt);
Object principal = authentication.getPrincipal();
return new ResponseEntity<>(new JwtResponse(((User) principal).getUsername()), httpHeaders, HttpStatus.OK);
}
public Authentication logUser(String appUsername, String password) {
Authentication authentication = authenticationManagerBuilder.getObject()
.authenticate(new UsernamePasswordAuthenticationToken(appUsername, password));
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
}
/JWT FILTER/
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
JwtUtils jwtUtils;
public static final String AUTHORIZATION_HEADER = "Authorization";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String jwt = resolveToken(request);
if (StringUtils.hasText(jwt)) {
Authentication authentication = jwtUtils.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
CodePudding user response:
Your decoded token looks like this :
{
"sub": "kent1",
"roles": "ADMIN",
"exp": 1661155145295,
"iat": 1660075145295
}
As far as I can see, the problem is in the user roles. Your roles should be in this format:
ROLE_ADMIN, or ROLE_CUSTOMER, etc.. (notice prefix ROLE_)
With @RolesAllowed("ADMIN")
annotation being set, this part :
.antMatchers("/users/all").hasRole("ADMIN")
is not needed.
BTW, in your JwtUtils
class I noticed this line of code:
claims.put("roles",authentication.getAuthorities());
With this line of code, you are setting this
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
list as a value for your "roles" key in map.
Since this list will contain whole GrantedAuthority
objects, that's not what Spring expects.
Assuming that your role table looks like this:
--------- ------------
| id_role | role_name |
--------- ------------
| 1 | ROLE_ADMIN |
| 2 | ROLE_USER |
--------- ------------
You can do something like this :
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
List<String> roles = new ArrayList<>();
for(GrantedAuthority authority: authorities){
if(authority.getAuthority().startsWith("ROLE")){
roles.add(authority.getAuthority());
}
}
claims.put("roles",roles);
and that will resolve problem that I just described.