Spring Security 与 JWT for REST API

Nuñ*_*ada 6 java rest jwt spring-boot

我有这门课:

\n
@Configuration\n@EnableWebSecurity\n@EnableGlobalMethodSecurity(prePostEnabled = true)\npublic class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter {\n\n    private static final String SALT = "fd&lkj\xc2\xa7isfs23#$1*(_)nof";\n\n    private final JwtAuthenticationEntryPoint unauthorizedHandler;\n    private final JwtTokenUtil jwtTokenUtil;\n    private final UserSecurityService userSecurityService;\n\n    @Value("${jwt.header}")\n    private String tokenHeader;\n\n\n    public ApiWebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtTokenUtil jwtTokenUtil,\n            UserSecurityService userSecurityService) {\n        this.unauthorizedHandler = unauthorizedHandler;\n        this.jwtTokenUtil = jwtTokenUtil;\n        this.userSecurityService = userSecurityService;\n    }\n\n    @Autowired\n    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {\n        auth\n                .userDetailsService(userSecurityService)\n                .passwordEncoder(passwordEncoder());\n    }\n\n    @Bean\n    public BCryptPasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));\n    }\n\n    @Bean\n    @Override\n    public AuthenticationManager authenticationManagerBean() throws Exception {\n        return super.authenticationManagerBean();\n    }\n\n    @Override\n    protected void configure(HttpSecurity httpSecurity) throws Exception {\n\n        httpSecurity\n                // we don't need CSRF because our token is invulnerable\n                .csrf().disable()\n\n                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()\n\n                // don't create session\n                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()\n                .authorizeRequests()\n                // Un-secure H2 Database\n                .antMatchers("/h2-console/**/**").permitAll()\n                .antMatchers("/api/v1/users").permitAll()\n                .antMatchers("/error").permitAll()\n                .anyRequest().authenticated();\n\n        // Custom JWT based security filter\n        JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);\n        httpSecurity\n                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);\n\n        // disable page caching\n        httpSecurity\n                .headers()\n                .frameOptions()\n                .sameOrigin()  // required to set for H2 else H2 Console will be blank.\n                .cacheControl();\n    }\n\n    @Override\n    public void configure(WebSecurity web) {\n\n        // AuthenticationTokenFilter will ignore the below paths\n        web\n                .ignoring()\n                .antMatchers(\n                        HttpMethod.POST,\n                        "/api/v1/auth"\n                )\n                .antMatchers(\n                        HttpMethod.POST,\n                        "/api/v1/users"\n                )\n                .antMatchers(\n                        HttpMethod.GET,\n                        "/api/v1/countries"\n                );\n\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\n
@Provider\n@Slf4j\npublic class JwtAuthorizationTokenFilter extends OncePerRequestFilter {\n\n    private UserDetailsService userDetailsService;\n    private JwtTokenUtil jwtTokenUtil;\n    private String tokenHeader;\n\n    public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService,\n            JwtTokenUtil jwtTokenUtil,\n            String tokenHeader) {\n        this.userDetailsService = userDetailsService;\n        this.jwtTokenUtil = jwtTokenUtil;\n        this.tokenHeader = tokenHeader;\n    }\n\n\n    @Override\n    protected boolean shouldNotFilter(HttpServletRequest request) {\n        return new AntPathMatcher().match("/api/v1/users",\n                request.getServletPath());\n    }\n\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException,\n            IOException {\n\n        log.info("processing authentication for '{}'", request.getRequestURL());\n        log.info("tokenHeader '{}'", tokenHeader);\n\n        final String requestHeader = request.getHeader(this.tokenHeader);\n\n        log.info("requestHeader '{}'", requestHeader);\n\n        String username = null;\n        String authToken = null;\n\n        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {\n            authToken = requestHeader.substring(7);\n\n            log.info("authToken '{}'", authToken);\n\n            try {\n                username = jwtTokenUtil.getUsernameFromToken(authToken);\n            } catch (IllegalArgumentException e) {\n                logger.info("an error occured during getting username from token", e);\n            } catch (ExpiredJwtException e) {\n                logger.info("the token is expired and not valid anymore", e);\n            }\n        } else {\n            logger.info("couldn't find bearer string, will ignore the header");\n        }\n\n        log.info("checking authentication for user '{}'", username);\n\n        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {\n            logger.info("security context was null, so authorizating user");\n\n            // It is not compelling necessary to load the use details from the database. You could also store the information\n            // in the token and read it from it. It's up to you ;)\n            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);\n\n            // For simple validation it is completely sufficient to just check the token integrity. You don't have to call\n            // the database compellingly. Again it's up to you ;)\n            if (jwtTokenUtil.validateToken(authToken, userDetails)) {\n                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());\n                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));\n                log.info("authorizated user '{}', setting security context", username);\n                SecurityContextHolder.getContext().setAuthentication(authentication);\n            }\n        }\n        chain.doFilter(request, response);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\n
@RestController\n@Slf4j\npublic class AuthenticationRestController {\n\n\n    private static final Logger LOG = LoggerFactory.getLogger   (AuthenticationRestController.class);\n\n    @Value("${jwt.header}")\n    private String tokenHeader;\n\n    @Autowired\n    private AuthenticationManager authenticationManager;\n\n    @Autowired\n    private JwtTokenUtil jwtTokenUtil;\n\n    @Autowired\n    private UserSecurityService userSecurityService;\n\n    @PostMapping(path = "/api/v1/auth", consumes = "application/json", produces = "application/json")\n    public ResponseEntity<JwtAuthenticationResponse>\n    createAuthenticationToken(  @RequestBody JwtAuthenticationRequest authenticationRequest,\n            HttpServletRequest request) throws AuthenticationException {\n\n        LOG.info("authenticating {} " , authenticationRequest.getUsername());\n\n        authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());\n\n        // Reload password post-security so we can generate the token\n        final User userDetails = (User) userSecurityService.loadUserByUsername(authenticationRequest.getUsername());\n\n        if (!userDetails.isEnabled()) {\n            throw new UserDisabledException();\n        }\n\n        if (LOG.isDebugEnabled()) {\n            LOG.debug("UserDetails userDetails [ " + authenticationRequest.getUsername() + " ]");\n        }\n\n\n        final String token = jwtTokenUtil.generateToken(userDetails);\n\n        // Return the token\n        return ResponseEntity.ok(new JwtAuthenticationResponse(token));\n    }\n\n\n    @GetMapping(path = "${jwt.route.authentication.refresh}", consumes = "application/json", produces = "application/json")\n    public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {\n        String authToken = request.getHeader(tokenHeader);\n        final String token = authToken.substring(7);\n        String username = jwtTokenUtil.getUsernameFromToken(token);\n        JwtUser user = (JwtUser) userSecurityService.loadUserByUsername(username);\n\n        if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())) {\n            String refreshedToken = jwtTokenUtil.refreshToken(token);\n            return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));\n        } else {\n            return ResponseEntity.badRequest().body(null);\n        }\n    }\n\n    @ExceptionHandler({AuthenticationException.class})\n    public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {\n        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());\n    }\n\n    /**\n     * Authenticates the user. If something is wrong, an {@link AuthenticationException} will be thrown\n     */\n    private void authenticate(String username, String password) {\n\n        Objects.requireNonNull(username);\n        Objects.requireNonNull(password);\n\n\n        try {\n            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));\n        } catch (DisabledException e) {\n            e.printStackTrace();\n            throw new AuthenticationException("User is disabled!", e);\n        } catch (BadCredentialsException e) {\n            throw new AuthenticationException("Bad credentials!", e);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\n
@RestController\n@RequestMapping("/api/v1/styles")\n@Slf4j\npublic class StyleResourceController  {\n\n    \n    @PutMapping(path = "/{styleCode}")\n    @ResponseStatus(HttpStatus.OK)\n    public void setAlerts(@RequestHeader(value = "Authorization") String authHeader, @PathVariable String styleCode)\n\n            throws DataAccessException {\n\n        System.out.println("add style {} ");\n\n        final User user = authUserOnPath(authHeader);\n\n        System.out.println("user {} " + user);\n\n            protected User authUserOnPath(String authHeader) {\n\n        String authToken = authHeader.substring(7);\n\n        String username = jwtTokenUtil.getUsernameFromToken(authToken);\n        User user = userService.findByUserName(username);\n\n        if (user == null)\n            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "UserNotFound");\n\n        return user;\n\n    }\n\n\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\n
@Component\npublic class JwtTokenUtil implements Serializable {\n\n    //static final String CLAIM_KEY_USERNAME = "sub";\n    //static final String CLAIM_KEY_CREATED = "iat";\n    private static final long serialVersionUID = -3301605591108950415L;\n    // @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")\n    private Clock clock = DefaultClock.INSTANCE;\n\n    @Value("${jwt.secret}")\n    private String secret;\n\n    @Value("${jwt.expiration}")\n    private Long expiration;\n\n    public String getUsernameFromToken(String token) {\n        return getClaimFromToken(token, Claims::getSubject);\n    }\n\n    public Date getIssuedAtDateFromToken(String token) {\n        return getClaimFromToken(token, Claims::getIssuedAt);\n    }\n\n    public Date getExpirationDateFromToken(String token) {\n        return getClaimFromToken(token, Claims::getExpiration);\n    }\n\n    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {\n        final Claims claims = getAllClaimsFromToken(token);\n        return claimsResolver.apply(claims);\n    }\n\n    private Claims getAllClaimsFromToken(String token) {\n        return Jwts.parser()\n                .setSigningKey(secret)\n                .parseClaimsJws(token)\n                .getBody();\n    }\n\n    private Boolean isTokenExpired(String token) {\n        final Date expiration = getExpirationDateFromToken(token);\n        return expiration.before(clock.now());\n    }\n\n    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {\n        return (lastPasswordReset != null && created.before(lastPasswordReset));\n    }\n\n    private Boolean ignoreTokenExpiration(String token) {\n        // here you specify tokens, for that the expiration is ignored\n        return false;\n    }\n\n    public String generateToken(UserDetails userDetails) {\n        Map<String, Object> claims = new HashMap<>();\n        return doGenerateToken(claims, userDetails.getUsername());\n    }\n\n    private String doGenerateToken(Map<String, Object> claims, String subject) {\n        final Date createdDate = clock.now();\n        final Date expirationDate = calculateExpirationDate(createdDate);\n\n        return Jwts.builder()\n                .setClaims(claims)\n                .setSubject(subject)\n                .setIssuedAt(createdDate)\n                .setExpiration(expirationDate)\n                .signWith(SignatureAlgorithm.HS512, secret)\n                .compact();\n    }\n\n    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {\n        final Date created = getIssuedAtDateFromToken(token);\n        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)\n                && (!isTokenExpired(token) || ignoreTokenExpiration(token));\n    }\n\n    public String refreshToken(String token) {\n        final Date createdDate = clock.now();\n        final Date expirationDate = calculateExpirationDate(createdDate);\n\n        final Claims claims = getAllClaimsFromToken(token);\n        claims.setIssuedAt(createdDate);\n        claims.setExpiration(expirationDate);\n\n        return Jwts.builder()\n                .setClaims(claims)\n                .signWith(SignatureAlgorithm.HS512, secret)\n                .compact();\n    }\n\n    public Boolean validateToken(String token, UserDetails userDetails) {\n        JwtUser user = (JwtUser) userDetails;\n        final String username = getUsernameFromToken(token);\n        final Date created = getIssuedAtDateFromToken(token);\n        return (\n                username.equals(user.getUsername())\n                        && !isTokenExpired(token)\n                        && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())\n        );\n    }\n\n    private Date calculateExpirationDate(Date createdDate) {\n        return new Date(createdDate.getTime() + expiration * 1000);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但是当我从令牌获取用户时为空:

\n
17:19:22.017 [http-nio-1133-exec-8] INFO  c.d.c.JwtAuthorizationTokenFilter - tokenHeader 'Authorization'\n17:19:22.017 [http-nio-1133-exec-8] INFO  c.d.c.JwtAuthorizationTokenFilter - requestHeader 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2OTE3NjcxMzYsImlhdCI6MTYzMTI4NzEzNn0.C9s3dbjWNVyGdV5k0LXsNhMGMvPzboTx1J6sGEbfXVOP1CzCLeZFgVPQ4o8jgugvgURF3BcnsWAk7ygd7RCvdg'\n17:19:22.017 [http-nio-1133-exec-8] INFO  c.d.c.JwtAuthorizationTokenFilter - authToken 'eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2OTE3NjcxMzYsImlhdCI6MTYzMTI4NzEzNn0.C9s3dbjWNVyGdV5k0LXsNhMGMvPzboTx1J6sGEbfXVOP1CzCLeZFgVPQ4o8jgugvgURF3BcnsWAk7ygd7RCvdg'\n17:19:22.018 [http-nio-1133-exec-8] INFO  c.d.c.JwtAuthorizationTokenFilter - checking authentication for user 'null'\n
Run Code Online (Sandbox Code Playgroud)\n

小智 1

enter code here@Component 公共类 JwtUtil {

 @Value("${jwt.secret}")
    private String secret;

 @Value("${jwt.expiration}") 
 private Long expiration;
 
 Date now =new Date();
 Date ExpirationDate =new Date(now.getTime()+ expiration*1000);

public User getUser(final String token) {
    Claims body = Jwts.parser()
            .setSigningKey(secret)
            .parseClaimsJws(token)
            .getBody();

    User u = new User();
    u.setUsername(body.getSubject());
    u.setUserId(Integer.parseInt((String) body.get("userId")));
    Set<Role> roles =new HashSet<>(); 
    roles.add(Role.CONSUMER);
    roles.add(Role.SELLER);
    u.setRoles(roles);
    return u;
}

public String generateToken(String username) {
       User u = new User();
      Claims claims = Jwts.claims().setSubject(username);
        claims.put("userId", u.getUserId());
        Set<Role> roles =new HashSet<>(); 
        roles.add(Role.CONSUMER);
        roles.add(Role.SELLER);
        claims.put("role",roles );

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration( ExpirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    
}

public boolean validateToken(final String token) {
    return getClaims(secret,token)
            .getExpiration()
            .after(new Date (System.currentTimeMillis()));
}

public Claims getClaims(String secret ,String token) {
    return Jwts.parser()
            .setSigningKey(secret)
            .parseClaimsJws(token)
            .getBody();
}
Run Code Online (Sandbox Code Playgroud)

}