JWT
Thu, Jul 1, 2021
閱讀時間 3 分鐘
JWT Dependency Inject
// 兩種 引入 jjwt , version 放 properties 內方便控管
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<jjwt.version>0.9.0</jjwt.version>
</properties>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
// 第二種
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
流程: 使用者帳密登入 -> 驗證後 -> 回應 token -> 取得token 放入 header 中 打 API
model
Request 帳密
public class AuthenticationRequest implements Serializable {
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;
}
}
Response 回應 token
public class AuthenticationResponse implements Serializable {
private final String jwt;
public AuthenticationResponse(String jwt) {
this.jwt = jwt;
}
public String getJwt() {
return jwt;
}
}
Config
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
// 建構子方式注入
private final MyUserDetailsService myUserDetailsService;
private final JwtRequestFilter jwtRequestFilter;
public MySecurityConfig(MyUserDetailsService myUserDetailsService, JwtRequestFilter jwtRequestFilter) {
this.myUserDetailsService = myUserDetailsService;
this.jwtRequestFilter = jwtRequestFilter;
}
// AuthenticationManager init 別的地方會用到
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// /authentication 請求可過
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/authentication")
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
最重要的 JWTUtil
@Service
public class JwtUtil {
private final String SECRET_KEY = "@*$&Secret&&$#";
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.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));
}
}
JWT Filter
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final MyUserDetailsService myUserDetailsService;
private final JwtUtil jwtUtil;
public JwtRequestFilter(MyUserDetailsService myUserDetailsService, JwtUtil jwtUtil) {
this.myUserDetailsService = myUserDetailsService;
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String authorzationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorzationHeader != null && authorzationHeader.startsWith("Bearer ")) {
jwt = authorzationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
系統可以取得 User 帳密的 service
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//implement it in system user module
return new User("Bill", "billisgood", new ArrayList<>());
}
}
JWT Controller
@RestController
public class JwtController {
private final AuthenticationManager authenticationManager;
private final MyUserDetailsService myUserDetailsService;
private final JwtUtil jwtUtil;
public JwtController(AuthenticationManager authenticationManager, MyUserDetailsService myUserDetailsService, JwtUtil jwtUtil) {
this.authenticationManager = authenticationManager;
this.myUserDetailsService = myUserDetailsService;
this.jwtUtil = jwtUtil;
}
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public ResponseEntity<?> sayHello() {
return ResponseEntity.ok().body("say hello success");
}
@RequestMapping(value = "/authentication", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUserName(), authenticationRequest.getPassword())
);
} catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password");
}
final UserDetails userDetails = myUserDetailsService.loadUserByUsername(authenticationRequest.getUserName());
final String jwt = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}