SpringSecurity code that implements the interface privileges granted and JWT check

By following two articles of the author before, I believe we already know what JWT is how to use, how to use in conjunction with Spring Security. This section then use the code to look at specific implementation process JWT login authentication and authentication.

First, the preparatory work environment

  • Spring Boot project and the establishment of integrated Spring Security, the project can start properly
  • Write a controller GET HTTP method by the service interface, such as: "/ hello"
  • Achieve the most basic dynamic data validation and distribution of competences, namely the interface and achieve UserDetailsService UserDetails interface. Both interfaces are provided verification information users, roles, permissions, etc. to the Spring Security Interface
  • If you've ever tried Spring Security's formLogin registration mode () Please HttpSecurity configuration section configuration formLogin all removed. Because JWT full use JSON interface not submitted from the form.
  • HttpSecurity configuration must be coupled with csrf (). Disable (), that is, temporarily turn off cross-site attacks CSRF defense. This is not safe, we do deal with the following chapters.

The contents of the above, we have already mentioned in the previous article. If you still are not familiar with, you can look at the article before this number.

## Second, the development tools JWT
incorporated by JWT Kit jjwt coordinate maven

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

Custom follows application.yml added in some configurations on the JWT

jwt: 
  header: JWTHeaderName
  secret: aabbccdd  
  expiration: 3600000   
  • Which is the name of the HTTP header Header carrying JWT token. Although I called here JWTHeaderName, but in the actual production readability, the worse the more secure.
  • secret information is used as the basis JWT of encryption and decryption. Although I have written here died in the configuration file, but in the actual production is usually not written directly in the configuration file inside. But through the application startup parameters passed, and need to be modified on a regular basis.
  • JWT expiration token is valid.

Spring write a tool like Boot configuration automatically loaded.

@Data
@ConfigurationProperties(prefix = "jwt")    //配置自动加载,prefix是配置的前缀
@Component
public class JwtTokenUtil implements Serializable {

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


    /**
     * 生成token令牌
     *
     * @param userDetails 用户
     * @return 令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);
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return 是否过期
     */
    public Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @return 新令牌
     */
    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;
    }

    /**
     * 验证令牌
     *
     * @param token       令牌
     * @param userDetails 用户
     * @return 是否有效
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        SysUser user = (SysUser) userDetails;
        String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token));
    }


    /**
     * 从claims生成令牌,如果看不懂就看谁调用它
     *
     * @param claims 数据声明
     * @return 令牌
     */
    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();
    }

    /**
     * 从令牌中获取数据声明,如果看不懂就看谁调用它
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

}

The method of using the above code is provided io.jsonwebtoken.jjwt development JWT token generation, refresh tools.

Third, the development logon interfaces (acquired Token Interface)

  • "/ Authentication" login authentication interface is used, and returned to the client generates JWT
  • "/ Refreshtoken" interface is used to refresh JWT, update JWT token is valid
@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,"用户名密码不能为空"));
        }
        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 of the token business logic written in JwtAuthService

  • login methods first with a user name, password login authentication. If the validation fails BadCredentialsException throws an exception. If authentication is successful, the program continues to go down, generating JWT response to the front-end
  • refreshToken refresh method only in the case of JWT token is not expired, expired will not be refreshed. 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) {
        //使用用户名密码进行登录验证
        UsernamePasswordAuthenticationToken upToken = 
                    new UsernamePasswordAuthenticationToken( username, password );
        Authentication authentication = authenticationManager.authenticate(upToken);  
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //生成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 the AuthenticationManager to use, so in succession WebSecurityConfigurerAdapter of SpringSecurity implementation class configuration, the AuthenticationManager declared as a Bean. And "/ authentication" and "/ refreshtoken" open access, how to open access to our previous article has been talked about.

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

Fourth, the interface to access authentication filter

When the first user logs, we will JWT token is returned to the client, the client should the token be saved. Interface requests during the time the token belt, placed inside the HTTP header, header name and jwt.header to the same configuration, so that the server can be resolved to. Here we define an interceptor:

  • Intercept request interfaces, token obtaining request from the request, parse the token from the username
  • The system then obtains a user (from a database, or other storage medium thereof) by UserDetailsService
  • The consistency JWT token and user information, the user authentication system with a user input, and determines whether JWT expired. If not expired, so far it indicates that the user is indeed a user of the system.
  • But you are a system does not mean you can access all user interfaces. It is necessary to construct UsernamePasswordAuthenticationToken pass users, permissions information, and this information by authentication inform Spring Security. Spring Security will use this interface to access your judgment.
@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 {
    
       // 从这里开始获取 request 中的 jwt token
        String authHeader = request.getHeader(jwtTokenUtil.getHeader());
        log.info("authHeader:{}", authHeader);
        // 验证token是否存在
        if (authHeader != null && StringUtils.isNotEmpty(authHeader)) {
           // 根据token 获取用户名
            String username = jwtTokenUtil.getUsernameFromToken(authHeader);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                // 通过用户名 获取用户的信息
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                
                // 验证JWT是否过期
                if (jwtTokenUtil.validateToken(authHeader, userDetails)) {
                    //加载用户、角色、权限信息,Spring Security根据这些信息判断接口的访问权限
                    UsernamePasswordAuthenticationToken authentication 
                            = new UsernamePasswordAuthenticationToken(userDetails, null, 
                                                                      userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource()
                                            .buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

In the spring Security class configuration (i.e., implementation class WebSecurityConfigurerAdapter configure (HttpSecurity http) allocation method, add the following configuration:

.sessionManagement()
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  • Because we used JWT, shows that the application of our application is a separate front and rear end, so we can prohibit the use of open STATELESS session. Of course, this is not absolute, before and after the end of the separation of application can also be used by a number of ways session, the core content of this article do not repeat them.
  • Our custom jwtAuthenticationTokenFilter, loaded into the front UsernamePasswordAuthenticationFilter.

Five test:

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

file

Here we visit a simple interface we define the "/ hello", but will not deliver JWT token, the result is to prohibit access. When we step on the token is returned, transferred to the header, the interface can be a normal response hello results.

file

Look forward to your attention

Guess you like

Origin www.cnblogs.com/zimug/p/11974554.html