SpringSecurity code implements JWT interface privilege granting and validation

Through the instructions in the first two articles, I believe you already know what JWT is, how to use it, and how to use it in conjunction with Spring Security.Then this section uses the code to implement the process of JWT login authentication and authentication.

I. Environmental Preparation

  • Build a Spring Boot project and integrate Spring Security so that the project can start properly
  • Write an HTTP GET method service interface through the controller, such as: "/hello"
  • Implement the most basic dynamic data validation and permission assignment, namely, the UserDetailsService and UserDetails interfaces.Both interfaces are interfaces that provide Spring Security with user, role, permission, and other verification information
  • If you have learned Spring Security's formLogin login mode, remove all the formLogin() configuration segments from the HttpSecurity configuration.Because JWT uses the JSON interface entirely, there is no from form submission.
  • The HttpSecurity configuration must include csrf().disable(), which temporarily turns off the defense against cross-station attacks against CSRF.That's not safe. We'll deal with it later.

That's what we've already said in previous articles.If you are still unfamiliar, you can look at the articles before this number.

##2. Developing JWT tool classes
Introducing JWT toolkit jjwt with maven coordinates

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

Add the following custom configurations to application.yml about JWT

jwt: 
  header: JWTHeaderName
  secret: aabbccdd  
  expiration: 3600000   
  • Where header is the name of the header that carries the HTTP token JWT.Although I'm called JWTHeaderName here, the less readable it is in production, the safer it is.
  • secret is the key used to encrypt and decrypt the underlying JWT information.Although I'm dead in the configuration file here, it's not usually written directly in the configuration file in actual production.Instead, it is passed through the application's startup parameters, which need to be modified periodically.
  • expiration is the valid time of the JWT token.

Write a Spring Boot configuration autoloaded tool class.

@Data
@ConfigurationProperties(prefix = "jwt")    //Configuration autoload, prefix is prefix of configuration
@Component
public class JwtTokenUtil implements Serializable {

    private String secret;
    private Long expiration;
    private String header;


    /**
     * Generate token token
     *
     * @param userDetails user
     * @return Token token
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put("sub", userDetails.getUsername());
        claims.put("created", new Date());
        return generateToken(claims);
    }

    /**
     * Get user name from token
     *
     * @param token token
     * @return User name
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * Determine whether a token is expired
     *
     * @param token token
     * @return Is it expired
     */
    public Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * refresh token
     *
     * @param token Original token
     * @return New token
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put("created", new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * Authentication token
     *
     * @param token       token
     * @param userDetails user
     * @return Is it valid
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        SysUser user = (SysUser) userDetails;
        String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token));
    }


    /**
     * Generate a token from claims and see who calls it if you don't understand it
     *
     * @param claims Data declaration
     * @return token
     */
    private String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + expiration);
        return Jwts.builder().setClaims(claims)
                            .setExpiration(expirationDate)
                            .signWith(SignatureAlgorithm.HS512, secret)
                            .compact();
    }

    /**
     * Get the data declaration from the token and see who calls it if you don't understand it
     *
     * @param token token
     * @return Data declaration
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

}

The code above is to develop a tool class for generating and refreshing JWT tokens using the methods provided by io.jsonwebtoken.jwt.

3. Develop login interface (get Token interface)

  • The'/authentication'interface is used for login validation and generates a JWT that is returned to the client
  • The'/refreshtoken'interface is used to refresh the JWT and update the validity of the JWT token
@RestController
public class JwtAuthController {

    @Resource
    private JwtAuthService jwtAuthService;

    @PostMapping(value = "/authentication")
    public AjaxResponse login(@RequestBody Map<String, String> map) {
        String username = map.get("username");
        String password = map.get("password");
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            return AjaxResponse.error(
                new CustomException(CustomExceptionType.USER_INPUT_ERROR,"User name password cannot be empty"));
        }
        return AjaxResponse.success(jwtAuthService.login(username, password));
    }

    @PostMapping(value = "/refreshtoken")
    public AjaxResponse refresh(@RequestHeader("${jwt.header}") String token) {
        return AjaxResponse.success(jwtAuthService.refreshToken(token));
    }

}

The core token business logic is written in JwtAuthService

  • Login method uses user name and password to authenticate login first.A BadCredentialsException exception is thrown if the validation fails.If the validation succeeds, the program continues down, generating a JWT response to the front end
  • The refreshToken method can only be refreshed if the JWT token has not expired and cannot be refreshed when it has expired.You need to log in again.
@Service
public class JwtAuthService {
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private JwtTokenUtil jwtTokenUtil;

    public String login(String username, String password) {
        //Logon authentication using username password
        UsernamePasswordAuthenticationToken upToken = 
                    new UsernamePasswordAuthenticationToken( username, password );
        Authentication authentication = authenticationManager.authenticate(upToken);  
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //Generate JWT
        UserDetails userDetails = userDetailsService.loadUserByUsername( username );
        return jwtTokenUtil.generateToken(userDetails);
    }

    public String refreshToken(String oldToken) {
        if (!jwtTokenUtil.isTokenExpired(oldToken)) {
            return jwtTokenUtil.refreshToken(oldToken);
        }
        return null;
    }
}

Because Authentication Manager is used, Authentication Manager is declared as a Bean in the Spring Security configuration implementation class that inherits the WebSecurityConfigurerAdapter.And open access to'/authentication'and'/refreshtoken', how to open access, as we've already mentioned in previous articles.

@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

4. Interface Access Authentication Filter

When the user logs on for the first time, we return the JWT token to the client, which should be saved by the client.When making an interface request, place the token strip inside the HTTP header whose name should match the configuration of jwt.header so that the server can resolve to it.Here we define an interceptor:

  • Intercept interface requests, get token from request, and resolve user name from token
  • Then get the system user (from the database, or other storage media) through UserDetailsService
  • Based on user information and JWT tokens, verify the consistency of system user and user input, and determine whether the JWT is expired.If it does not expire, this indicates that the user is really a user of the system.
  • However, being a system user does not mean that you have access to all interfaces.So you need to construct a UsernamePasswordAuthenticationToken to pass user, permission information, and tell Spring Security through authentication.Spring Security uses this to determine your interface access rights.
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private MyUserDetailsService userDetailsService;

    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
    
       // Get jwt token in request from here
        String authHeader = request.getHeader(jwtTokenUtil.getHeader());
        log.info("authHeader: {}", authHeader);
        // Verify token exists
        if (authHeader != null && StringUtils.isNotEmpty(authHeader)) {
           // Get user name from token
            String username = jwtTokenUtil.getUsernameFromToken(authHeader);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                // Get user information by user name
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                
                // Verify JWT is out of date
                if (jwtTokenUtil.validateToken(authHeader, userDetails)) {
                    //Load user, role, permission information from which Spring Security determines the access permissions of an interface
                    UsernamePasswordAuthenticationToken authentication 
                            = new UsernamePasswordAuthenticationToken(userDetails, null, 
                                                                      userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource()
                                            .buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

In the configuration method of spring Security's configuration class (that is, the WebSecurityConfigurerAdapter implementation class's configure(HttpSecurity http), add the following configuration:

.sessionManagement()
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  • Because we use JWT, which indicates that our application is a front-end and back-end separate application, we can turn on STATELESS to prohibit session usage.Of course, this is not absolute. Front-end and back-end separated applications can also use sessions through some means, which is not the core content of this article without further discussion.
  • Load our custom jwtAuthenticationTokenFilter in front of UsernamePasswordAuthenticationFilter.

5. Test:

Test the login interface, that is, the interface to get token.Enter the correct user name and password to get token.

Below we access a simple interface'/hello'that we defined, but we do not pass a JWT token, and access is disabled as a result.When we pass the token returned from the previous step into the header, we will be able to respond properly to hello's interface results.

Looking forward to your attention

Tags: Java Spring JSON Maven less

Posted on Wed, 04 Dec 2019 00:45:17 -0800 by stupid girl!