Role Based Authorization using JWT - Spring security - spring-boot

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

Related

Is it possible to get refresh token using OidcUserRequest?

I am loading oidcUser from OidcUserRequest in my Oauth2UserService implementation class.
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser user = delegate.loadUser(userRequest);
List<GrantedAuthority> rolesAsAuthorities = getRolesAsAuthorities(user);
CustomOidcUserDetailsImpl customUser = new CustomOidcUserDetailsImpl(user, rolesAsAuthorities);
customUser.setFullName(getFullName(user));
customUser.setTelephone(getTelephone(user));
customUser.setEmail(getEmail(user));
return customUser;
}
The problem is that i just can get OauthAccessToken and IdToken from OidcUserRequest. Are there any ways of getting Oauth2RefreshToken in my service?
I get id,access,refresh tokens if i exchange authorization code for tokens manually.
#Autowired
private OAuth2AuthorizedClientService authorizedClientService;
Authentication authentication =SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthorizedClient client = authorizedClientService
.loadAuthorizedClient(
"wso2", // client registrationId
authentication.getName());
Oauth2RefreshToken refreshToken = client.getRefreshtoken();

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();

#PreAuthorize returns 403

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,...

How to create multiple implementations of UserDetailsService in Spring Boot

I want to customize login API in spring boot. For a single kind of user, I created a implementation of UserDetailsService and it worked perfectly fine. Now, I want to create 3 different kinds of users, i.e., 3 different authorities. I don't think a single implementation can help me here. If I create 3 different implementations, and try using #Qualifier, how do I call a specific implementation ?
Any sort of help is appreciated! Below is the code for Login Endpoint of single kind Of user.
private static Logger logger = LogManager.getLogger();
#Value("${jwt.expires_in}")
private int EXPIRES_IN;
#Autowired
AuthenticationManager authManager;
#Autowired
TokenHelper tokenHelper;
#Autowired
ObjectMapper objectMapper;
#Autowired
PrincipalRepository principalRepository;
private boolean isAuthenticated(Authentication authentication) {
return authentication != null && !(authentication instanceof AnonymousAuthenticationToken) && authentication.isAuthenticated();
}
#PostMapping("/principal")
public ResponseEntity<Object[]> loginPrincipal(#RequestParam(name ="username") String username,
#RequestParam(name ="password") String password){
logger.info("In login api");
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
logger.error("Invalid Request!");
return ResponseEntity.badRequest().header("reason", "bad request").body(null);
}
UsernamePasswordAuthenticationToken authReq =
new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authManager.authenticate(authReq);
boolean isAuthenticated = isAuthenticated(authentication);
if (!isAuthenticated) {
logger.error("Not authenticated");
return ResponseEntity.badRequest().body(null);
}
Principal principal = null;
try {
principal = principalRepository.findByUserName(username);
}catch(Exception e) {
logger.error("Couldn't retrieve user");
return ResponseEntity.badRequest().header("reason", "username not found").body(null);
}
String jwt = tokenHelper.generateToken( username );
SecurityContextHolder.getContext().setAuthentication(authentication);
UserTokenState userTokenState = new UserTokenState(jwt, EXPIRES_IN);
return ResponseEntity.accepted().body(new Object[] {userTokenState, principal.getPrincipalID()});
}
Below is the code for UserDetailsService Implementation:
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
private PrincipalRepository principalRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("Loading user from db");
Principal principal = principalRepository.findByUserName(username);
if( principal == null){
System.out.println("User not found");
throw new UsernameNotFoundException("No user found. Username tried: " + username);
}
Set<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_PRINCIPAL"));
System.out.println("All done");
return new org.springframework.security.core.userdetails.User(principal.getUserName(), principal.getPassword(), grantedAuthorities);
}
}
Here, I am fetching a principal from db, because this implementation is principal-specific. I wanna create similar implementations for Student and Teacher and use them accordingly.
You don't need to create more than one implementation for UserDetailsService. Student, Teacher are also users, only one thing will differ these users is "authorities"(role & authorities) in the application if we look at from general view. Spring Security firstly checks "username" and "password" for authentication and after successful authentication, it checks "authorities" for authorization process in order to allow to use resources(methods, and etc) according to the business logic of the application.

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());

Resources