辅助类JwtUtil用于生成及验证JWT。
@Component
public class JwtUtil {
private String SECRET_KEY = "secret";
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
try {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("name", userDetails.getUsername());
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000*60*60*10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
}
用户管理服务
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
return new User("foo", "foo", new ArrayList<>());
}
}
request/response
public class AuthenticationRequest {
private String username;
private String password;
public AuthenticationRequest() {
}
public AuthenticationRequest(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "AuthenticationRequest{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
public class AuthenticationResponse {
private final String jwt;
public AuthenticationResponse(String jwt) {
this.jwt = jwt;
}
public String getJwt() {
return jwt;
}
}
JWT过滤器
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final String header = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (header != null && header.startsWith("Bearer")) {
jwt = header.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(token);
}
}
filterChain.doFilter(request, response);
}
}
注册security configurer以及JWT过滤器。
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
先访问authenticate取到JWT,才能访问/hello。
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "Hello world!";
}
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtTokenUtil;
@Autowired
private AuthenticationManager authenticationManager;
@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticateToken(@RequestBody AuthenticationRequest request) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
} catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password", e);
} catch (AuthenticationException e) {
e.printStackTrace();
}
final UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
final String jwt = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}
JWT测试rest client脚本
POST http://localhost:8080/authenticate HTTP/1.1
content-type: application/json
{
"username": "foo",
"password": "foo"
}
###
// 从上面返回的结果,取出JWT代替下面的TOKEN,不要带引号!!!
GET http://localhost:8080/hello HTTP/1.1
Authorization: Bearer TOKEN
###
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- 视频中强调要用到jaxb-api,不过发现去掉了也没问题 -->
<!-- <dependency>-->
<!-- <groupId>javax.xml.bind</groupId>-->
<!-- <artifactId>jaxb-api</artifactId>-->
<!-- <version>2.3.1</version>-->
<!-- </dependency>-->
---------------------------
知道的更多,不知道的也更多
---------------------------