#PreAuthorize returns 403 - spring-boot

I have a following method in the controller
#GetMapping("/hello")
#PreAuthorize("hasRole('ADMIN')")
public String hello() {
return "Hello " + JWTRequestFilter.UserClaim;
}
When a user who has the ADMIN role tries to access the /hello, 403 is returned. I have enabled the following in the websecurity class.
#EnableGlobalMethodSecurity(prePostEnabled = true)
Below is the JWT token.
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzZW50aHVyYW4iLCJSb2xlcyI6WyJBRE1JTiIsIlVTRVIiXSwiZXhwIjoxNTkzMDE0NDE5LCJpYXQiOjE1OTI5Nzg0MTl9.-7lTav3Nux8WVafUBGXjOxtXcE-r0fpfjb7wM7hrg6w
Even the JWT token has the role but still i'm getting 403. Does this preauthorize annotation see the role from the JWT or does it make a DB call and check the role of a user.Even I have used the #PreAuthrize annotation but still getting the same behaviour. How to resolve this 403. Below I have attached the JWTRequestFilter class.
public class JWTRequestFilter extends OncePerRequestFilter {
#Autowired
private MyUserDetailService userDetailService;
#Autowired
private JWTUtil jwtUtil;
public static String UserClaim = "";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
UserClaim = username;
}
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails= this.userDetailService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
This is how I'm generating the JWT token and how I set the roles.
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
Set<String> Userroles = new HashSet<>();
User user = userRepository.findByUsername(userDetails.getUsername());
for(Role role:user.getRoles()){
Userroles.add(role.getName());
}
claims.put("Roles",Userroles.toArray());
return createToken(claims, userDetails.getUsername());
}

Suggested Approach to identify the issue
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails= this.userDetailService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
String authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining());
System.out.println("Authorities granted : " + authorities);
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
} else {
System.out.println("Not Valid Token);
}
} else {
System.out.println("No Token);
}
Outcome: Token was valid but authorities were not loaded
Authorities granted :
Suggested Solution
Fix the MyUserDetailService to load Authorities in userDetails

Spring adds the prefix ROLE_ to the authorities.
You can either implement a setter that appends the role prefix.
Or another much simple way to do it would be to have a separate classe that implements GrantedAuthority interface
public class UserRole implements GrantedAuthority {
private MyRole role;
#Override
public String getAuthority() {
return "ROLE_" + role.toString();
}
}
//MyRole is the enum with the different roles ADMIN,VIEWER,...

Related

I have implemented JWT token security in spring boot code. how will I get jwt token anywhere in my code? need to save audit

I have implemented jwt security token in spring boot by refering jwt security impemented videos.
So after login I get generated jwt token, For further end points to hit I need to pass jwt token from request header then re request will get authorize at dofilter() method in JwtAuthenticationTokenFilter class as shown below.
public class JwtAuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.header}")
private String tokenHeader;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String username = null;
String authToken = null;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String header = httpRequest.getHeader(this.tokenHeader);
if (header != null && header.startsWith("Bearer ")) {
authToken = header.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
But I need to get that jwt token anywhere i want in my code to get some data from token.
for example look below code
public static AuditDetails createAudit() {
AuditDetails auditDetails = new AuditDetails();
**auditDetails.setCreateUser(token.getUsername());**
auditDetails.setCreateTime(new Date());
return auditDetails;
}
so basically i need to get username from token to same audit details, but how am i suppose get token in that code or anywhere in the code?
The token is sent to your app via the header (tokenHeader)
Edit
If you do not want to use the content of your HttpServletRequest anywhere, you can use as per session, a value holder that you can Inject (autowire) in every service to utilize the submitted token. You can try the following
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyHolder {
private String authToken;
public String getAuthToken() {
return authToken;
}
public void setAuthToken(String authToken) {
this.authToken = authToken;
}
}
Change the token value in your JwtAuthenticationTokenFilter
#Autowired MyHolder myHolder;
// ...
String authToken = null;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String header = httpRequest.getHeader(this.tokenHeader);
if (header != null && header.startsWith("Bearer ")) {
authToken = header.substring(7); // Here is your token
// UPDATE THE TOKEN VALUE IN YOUR HOLDER HERE
myHolder.setAuthToken(authToken);
// ...
}
Access the token anywhere in your app by autowiring the MyHolder class
#Autowired MyHolder myHolder;
// ...
var token = myHolder.getAuthToken();

Role Based Authorization using JWT - Spring security

Hope you all are doing great! I am implementing JWT Role Based Authorization in spring boot.
I have been able to implement it. The tutorial that I am following is this
https://github.com/only2dhir/spring-security-jwt
The user is being sucessfully registered. And then I assign that user a role like ADMIN.
Now I have this api #GetMapping("/users")
that should be accessed by ADMIN. However when I access this api, it gives me this error
java.lang.NullPointerException: Cannot invoke "Object.toString()" because the return value of
"io.jsonwebtoken.Claims.get(Object)" is null
This errors comes from these methods:
JwtTokenUtil:
public static final String AUTHORITIES_KEY = "scopes";
UsernamePasswordAuthenticationToken getAuthentication(final String token, final Authentication
exsitingAuth, final UserDetails userDetails){
final JwtParser jwtParser = Jwts.parser().setSigningKey(secret);
final Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
final Claims claims = claimsJws.getBody();
final Collection<? extends GrantedAuthority> authorities=
java.util.Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
}
This line gives error
java.util.Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
Creating authentication:
UserDetailsWithToken UserDetailsWithToken = new UserDetailsWithToken();
authenticate(authenticationRequest.getEmpID(),
authenticationRequest.getPswd());
final UserDetails userDetails =
userDetailsService.loadUserByUsername(authenticationRequest.getEmpID());
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getEmpID(),
authenticationRequest.getPswd()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
final String token =
jwtTokenUtil.generateToken(userDetails,authentication);
in your code you are not setting your authorities in the jwt token try setting it:
return doGenerateToken(authorities, userDetails.getUsername());
...
private String doGenerateToken(String authorities, String subject) {
return
Jwts.builder()
.claim(AUTHORITIES_KEY, authorities)
.setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 120000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
make sure to create your authentication correctly
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken("username", "myPassword");
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication.getPrincipal(), authentication);
I guess you are missing to add the authorities.
See the code here .... I eidted my answer to create a token ...
public String generateToken(UserDetails userDetails, Authentication
authentication) {
List<String> roles = user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
return doGenerateToken(userDetails.getUsername(), roles);
}
ANother method
public String doGenerateToken(String username, List<String> roles) {
try {
Claims claims = Jwts.claims().setSubject(username);
claims.put("username", roles);
claims.put(AUTHORITIES_KEY, username);
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 120000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
.compact();
} catch (InvalidKeyException e) {
logger.error(e.getMessage(), e);
}
return "";
}
Add the authorities string and pass it to doGenerateToken method.
Thanks,
Atul

How to perform Auth0 JWT token user Role-based authorization

I'm writing Spring Boot REST API, and I'm using JWT tokens. Now, I'm trying to create role-based authorization.
This is the tutorial/implementation that I'm using.
https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
I expanded this implementation with additional Role entity, and added #ManyToMany mapping to ApplicationUser Entity.
Now, as far as I understands, user roles should be added to token (during its creation).
So, this is an existing code:
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
String token = JWT.create()
.withSubject(((User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(HMAC512(SECRET.getBytes()));
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
I guess user roles should be added there. There is a function:
withArrayClaim(String Name, String[] items)
And there's my first problem: I'm not sure how to properly add this.
Then, is this fragments, which as far as I understand is place where token is verified:
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
What's bother me is fragment:
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
I don't understand why there is null (Inteliij highlights it as 'credentials') and this new ArrayList. Is there a place, where I should fetch roles from token, and add them?
I know, it's kinda broad scope question, but i couldn't find other solutions.
Or mayby there is an easier way to create simple JWT token authenitcation/authorization (role based).
Looking forward for your answers!
EDIT:
Or mayby is there more simple solutioni - not keeping user roles inside key - but only adding them in this 'second' part where null and new ArrayList is?
Just create the granted authorities based in the user roles and authenticate the user with it. Then the authenticated user principal will contain the roles.
Simple example:
UserEntity userEntity = userRepository.findUserByEmail(user); // this depends of course on your implementation
if (userEntity == null) return null;
List<RoleEntity> roles = userEntity.getRoles();
Collection<GrantedAuthority> authorities = new HashSet<>();
roles.forEach((role) -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
return new UsernamePasswordAuthenticationToken(user, null, authorities);
Even better, you can create a UserPrincipal that implements UserDetails from spring security.
public class UserPrincipal implements UserDetails {
private static final long serialVersionUID = 1L;
private final UserEntity userEntity;
public UserPrincipal(UserEntity userEntity){
this.userEntity = userEntity;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new HashSet<>();
// Get user Roles
Collection<RoleEntity> roles = userEntity.getRoles();
if(roles == null) return authorities;
roles.forEach((role) -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
return authorities;
}
#Override
public String getPassword() {
return this.userEntity.getEncryptedPassword();
}
#Override
public String getUsername() {
return this.userEntity.getEmail();
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}
And to use it:
UserEntity userEntity = userRepository.findUserByEmail(user);
if (userEntity == null) return null;
UserPrincipal userPrincipal = new UserPrincipal(userEntity);
return new UsernamePasswordAuthenticationToken(userPrincipal, null, userPrincipal.getAuthorities());

Can not get custom information in Authentication of spring-security

this is my AuthenticationProvider in authorization server
#Service
public class UmUserAuthenticationProvider implements AuthenticationProvider {
#Autowired
#Qualifier("UmUserDetailsService")
private UserDetailsService userDetailService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// TODO Auto-generated method stub
String username = authentication.getName();
String password = (String) authentication.getCredentials();
long userId = (new SecurityUtil()).checkUser(umUserMapper, username, password);
if (userId <= 0) {
throw new BadCredentialsException("login failed");
}
UserDetails user = userDetailService.loadUserByUsername(username);
//I've try different ways to put user detail in here
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null,
user.getAuthorities());
auth.setDetails(user);
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
and this is my resource server, I cant get what I set in Authentication,
getPrincipal is String
getDetails is type of org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails
#RequestMapping("/test")
public class UserController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String getRouters() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
logger.info(auth.getPrincipal().toString());
logger.info(auth.getDetails().toString());
JSONObject jo = new JSONObject();
return jo.toString();
}
the logger print
so How cat I get the custom detail in Authentication?
In the end, I solve this problem in a trick way
whatever I set in UsernamePasswordAuthenticationToken, I got the type of String, So I fill it with json string;
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
new JSONObject(user).toString(), password, userDetailService.getAuthorities(username));
so I can parse json string at the controller.
But I still want to know what cause it
I think the problem is, that you are setting the UserDetails on authentication, but it is overriden by the default.
A custom authentication converter might be the solution.
public class CustomAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
#Override
public AbstractAuthenticationToken convert(#NotNull final Jwt jwt) {
String username = jwt.getClaimAsString("username");
UserDetails user = userDetailService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken (jwt, user, userDetailService.getAuthorities(username)));
}
}

Spring Security not returning UserDetails object, only username

I had thought that my authorization implementation was done but when attempting to retrieve the UserDetails object, all I'm getting is the username.
I'm using oauth with the following particulars.
Configuring the AuthenticationManager:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
With this done, I can debug into my userDetailsService:
#Service
public class UserServiceImpl implements UserService, UserDetailsService {
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
MyUser persistedUser = userRepository.findByEmail(email);
if (persistedUser == null) {
throw new UsernameNotFoundException(String.format("The email %s doesn't exist", email));
}
List<GrantedAuthority> authorities = new ArrayList<>();
MyUser inMemoryUser = new MyUser(persistedUser.getEmail(), null, persistedUser.getEnabled(), false,
false, false, authorities);
return inMemoryUser;
}
}
This completes fine and my client gets back the JWT. But I found the following problem when debugging a later controller method.
#GetMapping
public #ResponseBody Iterable<Curriculum> getMyCurriculums(#AuthenticationPrincipal MyUser injectedUser) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
MyUser principle = (MyUser) auth.getPrincipal();
return curriculumService.findByUser(principle);
}
In this case, injectedUser = null, auth is an OAuth2Authentication, and principle is a String - the username. It should be MyUser
You should configure Spring Security to decode jwt token into MyUser object.
First define a custom OAuth2Authentication to encapsulate MyUser.
public class OAuth2AuthenticationUser extends OAuth2Authentication {
private MyUser myUser;
public OAuth2AuthenticationUser(OAuth2Request storedRequest, Authentication userAuthentication) {
super(storedRequest, userAuthentication);
}
public MyUser getMyUser() {
return myUser;
}
public void setMyUser(MyUser) {
this.myUser= myUser;
}
}
Then in a Security Configuration class configure jwt token decoding as follows:
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("SIGNING_KEY");
converter.setAccessTokenConverter(getAuthenticationAccessTokenConverter());
return converter;
}
private DefaultAccessTokenConverter getAuthenticationAccessTokenConverter() {
return new DefaultAccessTokenConverter() {
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication authentication = (OAuth2Authentication) super.extractAuthentication(map);
OAuth2AuthenticationUser authenticationUser =
new OAuth2AuthenticationUser(authentication.getOAuth2Request(), authentication.getUserAuthentication());
MyUser myUser = new MyUser();
// Example properties
myUser.setId(map.get("id") != null ? Long.valueOf(map.get("id").toString()) : null);
myUser.setUsername(map.get("user_name") != null ? map.get("user_name").toString() : null);
myUser.setFullName(map.get("fullName") != null ? map.get("fullName").toString() : null);
myUser.setCustomerId(map.get("customerId") != null ? Long.valueOf(map.get("customerId").toString()) : null);
myUser.setCustomerName(map.get("customerName") != null ? map.get("customerName").toString() : null);
// More other properties
authenticationUser.setMyUser(myUser);
return authenticationUser;
}
};
}
And then you have access to MyUser object from Spring Security context as follows:
private static MyUser getMyUser() {
OAuth2AuthenticationUser authentication = (OAuth2AuthenticationUser) SecurityContextHolder.getContext().getAuthentication();
return (authentication != null && authentication.getMyUser() != null ? authentication.getMyUser() : new MyUser());
}
This fits well in a stateless environment as database access for user details is minimized and all you need is jwt token.

Resources