Spring Security integrates JWT (front-end and back-end separation based on Spring Boot 2.x)

Keywords: JSON Spring github encoding

JWT

JSON Web Token (JWT) is an open standard (RFC 7519), which defines a compact, self-contained way to safely transfer information between parties as JSON objects. This information can be verified and trusted because it is digital signature.
Official website: https://jwt.io

  • JSON Web Token is composed of three parts: Header, Payload and Signature, which are connected by dots (.).
    A typical JWT looks like this:
    xxxxx.yyyyy.zzzzz
    Corresponding: Header.Payload.Signature

Header

The header typically consists of two parts: the type of token ("JWT") and the name of the algorithm (e.g. HMAC SHA256 or RSA).

For example:

{
"alg": "HS256",
"typ": "JWT"
}

Encoding this JSON with Base64 yields the first part of the JWT

Payload

The second part of JWT is payload, in which interactive information is usually stored. User names and user privileges (to avoid re-reading user privileges every request) and token expiration time are stored in XBoot.
For example:

{
"sub": "admin",
"authorities": "["add users","ROLE_ADMIN"]"
"exp": 1555554537
}
Base64 encoding payload gives you the second part of JWT

Note: Signatures are not encrypted. Anyone can see the contents of JWT unless they are encrypted. Therefore, do not place plaintext sensitive information into JWT.

Signature

Signature is used to verify whether the message has been changed during delivery, and for token using private key signature, it can also verify whether the sender of JWT is what it calls the sender.
For example:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

JWT Disadvantage: JWT is irrevocable unless the set expiration time is reached and it is troublesome to refresh the Token mechanism. Solution: XBoot configuration uses Redis record, no request automatically invalidates within 30 minutes, token can be managed at any time, see the code for details.

Spring Security Integration

  • Add dependency
<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
  • Spring Security Core Configuration Entry
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    ...

    @Autowired
    private AuthenticationSuccessHandler successHandler;

    @Autowired
    private AuthenticationFailHandler failHandler;

    @Autowired
    private RestAccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        ...

        registry.and()
                ...
                //Successful Handling Class
                .successHandler(successHandler)
                //fail
                .failureHandler(failHandler)
                //Closing Cross-Station Request Protection
                .csrf().disable()
                //JWT does not require session for front-end and back-end separation
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //Custom permission denial-of-processing class
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
                .and()
                //Adding a JWT filter requires that all requests except those that have been configured pass through the filter.
                .addFilter(new JWTAuthenticationFilter(authenticationManager()));
    }
}
  • Successful Handling Class
@Slf4j
@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Value("${xboot.tokenExpireTime}")
    private Integer tokenExpireTime;

    @Value("${xboot.saveLoginTime}")
    private Integer saveLoginTime;

    @Override
    @SystemLog(description = "Login system", type = LogType.LOGIN)
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        //Users choose to save login status for several days
        String saveLogin = request.getParameter(SecurityConstant.SAVE_LOGIN);
        ...
        String username = ((UserDetails)authentication.getPrincipal()).getUsername();
        List<GrantedAuthority> authorities = (List<GrantedAuthority>) ((UserDetails)authentication.getPrincipal()).getAuthorities();
        List<String> list = new ArrayList<>();
        for(GrantedAuthority g : authorities){
            list.add(g.getAuthority());
        }
        // Successful landing to generate token
        String token = SecurityConstant.TOKEN_SPLIT + Jwts.builder()
                        //Subject put in user name
                        .setSubject(username)
                        //Custom Properties Put into User with Request Permission
                        .claim(SecurityConstant.AUTHORITIES, new Gson().toJson(list))
                        //Failure time
                        .setExpiration(new Date(System.currentTimeMillis() + tokenExpireTime * 60 * 1000))
                        //Signature algorithm and key
                        .signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY)
                        .compact();

        ResponseUtil.out(response, ResponseUtil.resultMap(true,200,"Login successfully", token));
    }
}
  • Failure Handling Class
@Slf4j
@Component
public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {

    ...

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {

        ...
        if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
            ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"ERROR Incorrect username or password"));
        } else if (e instanceof DisabledException) {
            ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"Account is disabled, please contact Administrator"));
        } else if (e instanceof LoginFailLimitException){
            ResponseUtil.out(response, ResponseUtil.resultMap(false,500,((LoginFailLimitException) e).getMsg()));
        } else {
            ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"Logon failure, other internal errors"));
        }
    }
}
  • Custom permission denial-of-processing class
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
            throws IOException, ServletException {

        ResponseUtil.out(response, ResponseUtil.resultMap(false,403,"Sorry, you don't have access rights."));
    }

}

Posted by bulldorc on Sat, 04 May 2019 06:30:39 -0700