【发布时间】:2021-11-07 02:39:05
【问题描述】:
我有这门课:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String SALT = "fd&lkj§isfs23#$1*(_)nof";
private final JwtAuthenticationEntryPoint unauthorizedHandler;
private final JwtTokenUtil jwtTokenUtil;
private final UserSecurityService userSecurityService;
@Value("${jwt.header}")
private String tokenHeader;
public ApiWebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtTokenUtil jwtTokenUtil,
UserSecurityService userSecurityService) {
this.unauthorizedHandler = unauthorizedHandler;
this.jwtTokenUtil = jwtTokenUtil;
this.userSecurityService = userSecurityService;
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userSecurityService)
.passwordEncoder(passwordEncoder());
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// Un-secure H2 Database
.antMatchers("/h2-console/**/**").permitAll()
.antMatchers("/api/v1/users").permitAll()
.antMatchers("/error").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity
.headers()
.frameOptions()
.sameOrigin() // required to set for H2 else H2 Console will be blank.
.cacheControl();
}
@Override
public void configure(WebSecurity web) {
// AuthenticationTokenFilter will ignore the below paths
web
.ignoring()
.antMatchers(
HttpMethod.POST,
"/api/v1/auth"
)
.antMatchers(
HttpMethod.POST,
"/api/v1/users"
)
.antMatchers(
HttpMethod.GET,
"/api/v1/countries"
);
}
}
和
@Provider
@Slf4j
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
private UserDetailsService userDetailsService;
private JwtTokenUtil jwtTokenUtil;
private String tokenHeader;
public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService,
JwtTokenUtil jwtTokenUtil,
String tokenHeader) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.tokenHeader = tokenHeader;
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return new AntPathMatcher().match("/api/v1/users",
request.getServletPath());
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException,
IOException {
log.info("processing authentication for '{}'", request.getRequestURL());
log.info("tokenHeader '{}'", tokenHeader);
final String requestHeader = request.getHeader(this.tokenHeader);
log.info("requestHeader '{}'", requestHeader);
String username = null;
String authToken = null;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
log.info("authToken '{}'", authToken);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
logger.info("an error occured during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.info("the token is expired and not valid anymore", e);
}
} else {
logger.info("couldn't find bearer string, will ignore the header");
}
log.info("checking authentication for user '{}'", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
logger.info("security context was null, so authorizating user");
// It is not compelling necessary to load the use details from the database. You could also store the information
// in the token and read it from it. It's up to you ;)
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// For simple validation it is completely sufficient to just check the token integrity. You don't have to call
// the database compellingly. Again it's up to you ;)
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
log.info("authorizated user '{}', setting security context", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
和
@RestController
@Slf4j
public class AuthenticationRestController {
private static final Logger LOG = LoggerFactory.getLogger (AuthenticationRestController.class);
@Value("${jwt.header}")
private String tokenHeader;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserSecurityService userSecurityService;
@PostMapping(path = "/api/v1/auth", consumes = "application/json", produces = "application/json")
public ResponseEntity<JwtAuthenticationResponse>
createAuthenticationToken( @RequestBody JwtAuthenticationRequest authenticationRequest,
HttpServletRequest request) throws AuthenticationException {
LOG.info("authenticating {} " , authenticationRequest.getUsername());
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
// Reload password post-security so we can generate the token
final User userDetails = (User) userSecurityService.loadUserByUsername(authenticationRequest.getUsername());
if (!userDetails.isEnabled()) {
throw new UserDisabledException();
}
if (LOG.isDebugEnabled()) {
LOG.debug("UserDetails userDetails [ " + authenticationRequest.getUsername() + " ]");
}
final String token = jwtTokenUtil.generateToken(userDetails);
// Return the token
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
}
@GetMapping(path = "${jwt.route.authentication.refresh}", consumes = "application/json", produces = "application/json")
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {
String authToken = request.getHeader(tokenHeader);
final String token = authToken.substring(7);
String username = jwtTokenUtil.getUsernameFromToken(token);
JwtUser user = (JwtUser) userSecurityService.loadUserByUsername(username);
if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())) {
String refreshedToken = jwtTokenUtil.refreshToken(token);
return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));
} else {
return ResponseEntity.badRequest().body(null);
}
}
@ExceptionHandler({AuthenticationException.class})
public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
/**
* Authenticates the user. If something is wrong, an {@link AuthenticationException} will be thrown
*/
private void authenticate(String username, String password) {
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
e.printStackTrace();
throw new AuthenticationException("User is disabled!", e);
} catch (BadCredentialsException e) {
throw new AuthenticationException("Bad credentials!", e);
} catch (Exception e) {
e.printStackTrace();
}
}
}
和
@RestController
@RequestMapping("/api/v1/styles")
@Slf4j
public class StyleResourceController {
@PutMapping(path = "/{styleCode}")
@ResponseStatus(HttpStatus.OK)
public void setAlerts(@RequestHeader(value = "Authorization") String authHeader, @PathVariable String styleCode)
throws DataAccessException {
System.out.println("add style {} ");
final User user = authUserOnPath(authHeader);
System.out.println("user {} " + user);
protected User authUserOnPath(String authHeader) {
String authToken = authHeader.substring(7);
String username = jwtTokenUtil.getUsernameFromToken(authToken);
User user = userService.findByUserName(username);
if (user == null)
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "UserNotFound");
return user;
}
}
}
和
@Component
public class JwtTokenUtil implements Serializable {
//static final String CLAIM_KEY_USERNAME = "sub";
//static final String CLAIM_KEY_CREATED = "iat";
private static final long serialVersionUID = -3301605591108950415L;
// @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")
private Clock clock = DefaultClock.INSTANCE;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getIssuedAtDateFromToken(String token) {
return getClaimFromToken(token, Claims::getIssuedAt);
}
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(secret)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
}
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
private Boolean ignoreTokenExpiration(String token) {
// here you specify tokens, for that the expiration is ignored
return false;
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
final Date created = getIssuedAtDateFromToken(token);
return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
&& (!isTokenExpired(token) || ignoreTokenExpiration(token));
}
public String refreshToken(String token) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(createdDate);
claims.setExpiration(expirationDate);
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getIssuedAtDateFromToken(token);
return (
username.equals(user.getUsername())
&& !isTokenExpired(token)
&& !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
);
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration * 1000);
}
}
但是当我从令牌中获取用户时为空:
17:19:22.017 [http-nio-1133-exec-8] INFO c.d.c.JwtAuthorizationTokenFilter - tokenHeader 'Authorization'
17:19:22.017 [http-nio-1133-exec-8] INFO c.d.c.JwtAuthorizationTokenFilter - requestHeader 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2OTE3NjcxMzYsImlhdCI6MTYzMTI4NzEzNn0.C9s3dbjWNVyGdV5k0LXsNhMGMvPzboTx1J6sGEbfXVOP1CzCLeZFgVPQ4o8jgugvgURF3BcnsWAk7ygd7RCvdg'
17:19:22.017 [http-nio-1133-exec-8] INFO c.d.c.JwtAuthorizationTokenFilter - authToken 'eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2OTE3NjcxMzYsImlhdCI6MTYzMTI4NzEzNn0.C9s3dbjWNVyGdV5k0LXsNhMGMvPzboTx1J6sGEbfXVOP1CzCLeZFgVPQ4o8jgugvgURF3BcnsWAk7ygd7RCvdg'
17:19:22.018 [http-nio-1133-exec-8] INFO c.d.c.JwtAuthorizationTokenFilter - checking authentication for user 'null'
【问题讨论】:
-
您的令牌不包含用户名,因此请将您的精力集中在创建部分。
-
在您的令牌中找不到任何与用户相关的数据。粘贴您的 JWT 令牌 here。当你创建它们时,你需要在你的令牌中添加与用户相关的数据。
-
如 sam 和 ray 所示,您的令牌不包含任何用户信息(例如,如果您将令牌复制并粘贴到 jet.io 中,它将在有效负载中不提供
sub值)。 createcreateAuthenticationToken方法看起来和generateToken一样好。请尝试在此行之前调试您的 ÙserDetails` 实例:final String token = jwtTokenUtil.generateToken(userDetails);。另外,请确保您的UserDetails正确实现了getUsername方法,有时我们从数据库中派生它并且我们没有正确实现它。尝试调试方法... -
doGenerateToken以及查看subject变量的值。我希望我能帮上忙。 -
对不起,我想说jwt.io,而不是jet.io。我的电脑翻译器。
标签: java spring-boot jwt rest