Spring with JWT auth, get current user

24,319

Solution 1

In your case the @AuthenticationPrincipal will return a string with the username, you can get the user by calling the repository in your controller and getting the user by the username or declaring the repository as a @Bean and do the folowing:

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

//Get the repository
private UserRepository userRepository;

public JWTAuthorizationFilter(AuthenticationManager authManager) {
    super(authManager);
}

@Override
protected void doFilterInternal(HttpServletRequest req,
                                HttpServletResponse res,
                                FilterChain chain) throws IOException, ServletException {
    String header = req.getHeader(HEADER_STRING);

    if (header == null || !header.startsWith(TOKEN_PREFIX)) {
        chain.doFilter(req, res);
        return;
    }

    UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

    SecurityContextHolder.getContext().setAuthentication(authentication);
    chain.doFilter(req, res);
}

private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
    String token = request.getHeader(HEADER_STRING);
    if (token != null) {
        // parse the token.
        String user;
        try {
            user = Jwts.parser()
                    .setSigningKey(SECRET.getBytes())
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody()
                    .getSubject();
        } catch (SignatureException e) {
            return null;
        }

        //Get your user
        UserEntity userEntity = this.userRepository.findByUsername(user);

        if (user != null) {
             //Seting in your AuthenticationPrincipal the user
             return new UsernamePasswordAuthenticationToken(userEntity, null, new ArrayList<>());
        }

        return null;
    }
    return null;
}

}

Solution 2

Check if you are using suitable annotation, because one of them is deprecated.

Documentation - deprecated!

Documentation - fine!


In addition, be aware to resolve username (String) as an argument, not User type:

Annotation that is used to resolve Authentication.getPrincipal() to a method argument.

Check this topic as well! It can help.


I don't know if it is good practice (I'm not considered 'pro' in Spring yet), but in my personal project I get token from HttpServletRequest object passed in controller parameter. Then I use JwtTokenUtil class, which have getUserFormToken(String token); method to resolve user/username. It looks like this:

Controller

@Autowired
TestService testService;

@Autowired
UserService userService;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@RequestMapping(value="/test", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public List<Test> getTestsListByUserId(HttpServletRequest req){
    String token = req.getHeader(HEADER_STRING).replace(TOKEN_PREFIX,"");
    return testService.findByUserId(userService.findByUsername(jwtTokenUtil.getUsernameFromToken(token)));
}

JwtTokenUtil

@Component
public class JwtTokenUtil implements Serializable {

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(SIGNING_KEY)
            .parseClaimsJws(token)
            .getBody();
}

private Boolean isTokenExpired(String token) {
    final Date expiration = getExpirationDateFromToken(token);
    return expiration.before(new Date());
}

public String generateToken(User user) {
    return doGenerateToken(user.getUsername());
}

private String doGenerateToken(String subject) {

    Claims claims = Jwts.claims().setSubject(subject);
    claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));

    return Jwts.builder()
            .setClaims(claims)
            .setIssuer("issuer")
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
            .signWith(SignatureAlgorithm.HS256, SIGNING_KEY)
            .compact();
}

public Boolean validateToken(String token, UserDetails userDetails) {
    final String username = getUsernameFromToken(token);
    return (
           username.equals(userDetails.getUsername())
                   && !isTokenExpired(token));
    }

}

But I generally have different filters implementation according yo yours. If you are interested - I used this tutorial and implementation.

Solution 3

To retrieve a custom model i do next things:

Get model from Database and set it as Principal.

 private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            String user;
            try {
                user = Jwts.parser()
                        .setSigningKey(SECRET.getBytes())
                        .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                        .getBody()
                        .getSubject();
            } catch (SignatureException e) {
                return null;
            }

            // Get user model
            ApplicationUser userModel = userRepository.findByUsername(user);

            // Set it
            if (user != null && userModel != null) return new UsernamePasswordAuthenticationToken(userModel, null, new ArrayList<>());

            return null;
        }
        return null;
    }

Then in controller retrieve using @AuthenticationPrincipal annotation.

public ApplicationUser getCurrentUser(@AuthenticationPrincipal ApplicationUser user) {
    return user;
}

Solution 4

If this is still actual, I have just answered similar question here

Main point is to take authorization token from header:

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
    String token = request.getHeader("Authorization").split(" ")[1];

after that you can decode it and get parts which you need.

Share:
24,319

Related videos on Youtube

Alex
Author by

Alex

Updated on February 19, 2022

Comments

  • Alex
    Alex about 2 years

    I have Spring Boot REST application which uses JWT tokens for authorization. I want to get current logged user in controllers using @AuthenticationPrincipal annotation. But it always returns null if i return custom model from loadUserByUsername and auth stop working. My model implements UserDetails.

    I tried to extend the org.springframework.security.core.userdetails.User but i get rid errors from JWTAuthenticationFilter that default constructor not exists (ApplicationUser creds = new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);)

    Whats wrong?

    UserDetailsServiceImpl.java

    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
        private UserRepository userRepository;
    
        public UserDetailsServiceImpl(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            ApplicationUser applicationUser = userRepository.findByUsername(username);
            if (applicationUser == null) throw new UsernameNotFoundException(username);
    
            return applicationUser;
        }
    }
    

    ApplicationUser.java (model)

    @Entity
    @Table(name = "users")
    public class ApplicationUser implements UserDetails {
    
        private static final long serialVersionUID = 1L;
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private long id;
    
        @Column(unique = true, nullable = false)
        private String username;
    
        @Column(unique = true, nullable = false)
        private String email;
    
        @Column(nullable = false)
        private String password;
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return false;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return false;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return false;
        }
    
        @Override
        public boolean isEnabled() {
            return false;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    }
    

    JWTAuthenticationFilter

    public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
        private AuthenticationManager authenticationManager;
    
        public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
            super(new AntPathRequestMatcher(LOGIN_URL));
    
            this.authenticationManager = authenticationManager;
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest req,
                                                    HttpServletResponse res) throws AuthenticationException {
            try {
                ApplicationUser creds = new ObjectMapper()
                        .readValue(req.getInputStream(), ApplicationUser.class);
    
                return authenticationManager.authenticate(
                        new UsernamePasswordAuthenticationToken(
                                creds.getUsername(),
                                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 token = Jwts.builder()
                    .setSubject(((ApplicationUser) auth.getPrincipal()).getUsername())
                    .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                    .signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
                    .compact();
    
            res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
        }
    }
    

    JWTAuthorizationFilter

    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 header = req.getHeader(HEADER_STRING);
    
            if (header == null || !header.startsWith(TOKEN_PREFIX)) {
                chain.doFilter(req, res);
                return;
            }
    
            UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
    
            SecurityContextHolder.getContext().setAuthentication(authentication);
            chain.doFilter(req, res);
        }
    
        private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
            String token = request.getHeader(HEADER_STRING);
            if (token != null) {
                // parse the token.
                String user;
                try {
                    user = Jwts.parser()
                            .setSigningKey(SECRET.getBytes())
                            .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                            .getBody()
                            .getSubject();
                } catch (SignatureException e) {
                    return null;
                }
    
                if (user != null) return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
    
                return null;
            }
            return null;
        }
    }