鍑嗗
寮€濮嬫湰鏁欑▼鐨勬椂鍊欏笇鏈涘涓嬮潰鐭ヨ瘑鐐硅繘琛岀矖鐣ョ殑浜嗚В銆?/p>
- 鐭ラ亾 JWT 鐨勫熀鏈蹇?/li>
- 浜嗚В杩?Spring Security
鏈」鐩腑 JWT
瀵嗛挜鏄娇鐢ㄧ敤鎴疯嚜宸辩殑鐧诲叆瀵嗙爜锛岃繖鏍锋瘡涓€涓?token
鐨勫瘑閽ラ兘涓嶅悓锛岀浉瀵规瘮杈冨畨鍏ㄣ€?/p>
澶т綋鎬濊矾锛?/h3>
鐧诲叆锛?/strong>
- POST 鐢ㄦ埛鍚嶅瘑鐮佸埌 \login
- 璇锋眰鍒拌揪
JwtAuthenticationFilter
涓殑attemptAuthentication()
鏂规硶锛岃幏鍙?request 涓殑 POST 鍙傛暟锛屽寘瑁呮垚涓€涓?UsernamePasswordAuthenticationToken
浜や粯缁?AuthenticationManager
鐨?authenticate()
鏂规硶杩涜閴存潈銆?/li> -
AuthenticationManager
浼氫粠CachingUserDetailsService
涓煡鎵剧敤鎴蜂俊鎭紝骞朵笖鍒ゆ柇璐﹀彿瀵嗙爜鏄惁姝g‘銆?/li> - 濡傛灉璐﹀彿瀵嗙爜姝g‘璺宠浆鍒?
JwtAuthenticationFilter
涓殑successfulAuthentication()
鏂规硶锛屾垜浠繘琛岀鍚嶏紝鐢熸垚 token 杩斿洖缁欑敤鎴枫€?/li> - 璐﹀彿瀵嗙爜閿欒鍒欒烦杞埌
JwtAuthenticationFilter
涓殑unsuccessfulAuthentication()
鏂规硶锛屾垜浠繑鍥為敊璇俊鎭鐢ㄦ埛閲嶆柊鐧诲叆銆?/li>
璇锋眰閴存潈锛?/strong>
璇锋眰閴存潈鐨勪富瑕佹€濊矾鏄垜浠細浠庤姹備腑鐨?Authorization 瀛楁鎷垮彇 token锛屽鏋滀笉瀛樺湪姝ゅ瓧娈电殑鐢ㄦ埛锛孲pring Security 浼氶粯璁や細鐢?AnonymousAuthenticationToken()
鍖呰瀹冿紝鍗充唬琛ㄥ尶鍚嶇敤鎴枫€?/p>
- 浠绘剰璇锋眰鍙戣捣
- 鍒拌揪
JwtAuthorizationFilter
涓殑doFilterInternal()
鏂规硶锛岃繘琛岄壌鏉冦€?/li> - 濡傛灉閴存潈鎴愬姛鎴戜滑鎶婄敓鎴愮殑
Authentication
鐢?SecurityContextHolder.getContext().setAuthentication()
鏀惧叆 Security锛屽嵆浠h〃閴存潈瀹屾垚銆傛澶勫浣曢壌鏉冪敱鎴戜滑鑷繁浠g爜缂栧啓锛屽悗搴忎細璇︾粏璇存槑銆?/li>
鍑嗗 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.inlighting</groupId>
<artifactId>spring-boot-security-jwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-security-jwt</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
<!-- JWT 鏀寔 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>
<!-- cache 鏀寔 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- cache 鏀寔 -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<!-- cache 鏀寔 -->
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- ehcache 璇诲彇 xml 閰嶇疆鏂囦欢浣跨敤 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<!-- ehcache 璇诲彇 xml 閰嶇疆鏂囦欢浣跨敤 -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<!-- ehcache 璇诲彇 xml 閰嶇疆鏂囦欢浣跨敤 -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<!-- ehcache 璇诲彇 xml 閰嶇疆鏂囦欢浣跨敤 -->
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
pom.xml 閰嶇疆鏂囦欢杩欏潡娌℃湁浠€涔堝ソ璇寸殑锛屼富瑕佽鏄庝笅闈㈢殑鍑犱釜渚濊禆锛?/p>
<!-- ehcache 璇诲彇 xml 閰嶇疆鏂囦欢浣跨敤 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<!-- ehcache 璇诲彇 xml 閰嶇疆鏂囦欢浣跨敤 -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<!-- ehcache 璇诲彇 xml 閰嶇疆鏂囦欢浣跨敤 -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<!-- ehcache 璇诲彇 xml 閰嶇疆鏂囦欢浣跨敤 -->
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
鍥犱负 ehcache 璇诲彇 xml 閰嶇疆鏂囦欢鏃朵娇鐢ㄤ簡杩欏嚑涓緷璧栵紝鑰岃繖鍑犱釜渚濊禆浠?JDK 9 寮€濮嬫椂鏄€夐厤妯″潡锛屾墍浠ラ珮鐗堟湰鐨勭敤鎴烽渶瑕佹坊鍔犺繖鍑犱釜渚濊禆鎵嶈兘姝e父浣跨敤銆?/p>
鍩虹宸ヤ綔鍑嗗
鎺ヤ笅鏉ュ噯澶囦笅鍑犱釜鍩虹宸ヤ綔锛屽氨鏄柊寤轰釜瀹炰綋銆佹ā鎷熶釜鏁版嵁搴擄紝鍐欎釜 JWT 宸ュ叿绫昏繖绉嶅熀纭€鎿嶄綔銆?/p>
UserEntity.java
鍏充簬 role 涓轰粈涔堜娇鐢?GrantedAuthority 璇存槑涓嬶細鍏跺疄鏄负浜嗙畝鍖栦唬鐮侊紝鐩存帴鐢ㄤ簡 Security 鐜版垚鐨?role 绫伙紝瀹為檯椤圭洰涓垜浠偗瀹氳鑷繁杩涜澶勭悊锛屽皢鍏惰浆鎹负 Security 鐨?role 绫汇€?/p>
public class UserEntity {
public UserEntity(String username, String password, Collection<extends GrantedAuthority> role) {
this.username = username;
this.password = password;
this.role = role;
}
private String username;
private String password;
private Collection<extends GrantedAuthority> role;
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;
}
public Collection<extends GrantedAuthority> getRole() {
return role;
}
public void setRole(Collection<extends GrantedAuthority> role) {
this.role = role;
}
}
ResponseEntity.java
鍓嶅悗绔垎绂讳负浜嗘柟渚垮墠绔垜浠缁熶竴 json 鐨勮繑鍥炴牸寮忥紝鎵€浠ヨ嚜瀹氫箟涓€涓?ResponseEntity.java銆?/p>
public class ResponseEntity {
public ResponseEntity() {
}
public ResponseEntity(int status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
private int status;
private String msg;
private Object data;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
Database.java
杩欓噷鎴戜滑浣跨敤涓€涓?HashMap 妯℃嫙浜嗕竴涓暟鎹簱锛屽瘑鐮佹垜宸茬粡棰勫厛鐢?Bcrypt
鍔犲瘑杩囦簡锛岃繖涔熸槸 Spring Security 瀹樻柟鎺ㄨ崘鐨勫姞瀵嗙畻娉曪紙MD5 鍔犲瘑宸茬粡鍦?Spring Security 5 涓绉婚櫎浜嗭紝涓嶅畨鍏級銆?/p>
鐢ㄦ埛鍚?/th> | 瀵嗙爜 | 鏉冮檺 |
---|---|---|
jack | jack123 瀛?Bcrypt 鍔犲瘑鍚?/td> | ROLE_USER |
danny | danny123 瀛?Bcrypt 鍔犲瘑鍚?/td> | ROLE_EDITOR |
smith | smith123 瀛?Bcrypt 鍔犲瘑鍚?/td> | ROLE_ADMIN |
@Component
public class Database {
private Map<String, UserEntity> data = null;
public Map<String, UserEntity> getDatabase() {
if (data == null) {
data = new HashMap<>();
UserEntity jack = new UserEntity(
"jack",
"a$AQol1A.LkxoJ5dEzS5o5E.QG9jD.hncoeCGdVaMQZaiYZ98V/JyRq",
getGrants("ROLE_USER"));
UserEntity danny = new UserEntity(
"danny",
"anMJR6r7lvh9H2INtM2vtuA156dHTcQUyU.2Q2OK/7LwMd/I.HM12",
getGrants("ROLE_EDITOR"));
UserEntity smith = new UserEntity(
"smith",
"a$E86mKigOx1NeIr7D6CJM3OQnWdaPXOjWe4OoRqDqFgNgowvJW9nAi",
getGrants("ROLE_ADMIN"));
data.put("jack", jack);
data.put("danny", danny);
data.put("smith", smith);
}
return data;
}
private Collection<GrantedAuthority> getGrants(String role) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(role);
}
}
UserService.java
杩欓噷鍐嶆ā鎷熶竴涓?service锛屼富瑕佸氨鏄ā浠挎暟鎹簱鐨勬搷浣溿€?/p>
@Service
public class UserService {
@Autowired
private Database database;
public UserEntity getUserByUsername(String username) {
return database.getDatabase().get(username);
}
}
JwtUtil.java
鑷繁缂栧啓鐨勪竴涓伐鍏风被锛屼富瑕佽礋璐?JWT 鐨勭鍚嶅拰閴存潈銆?/p>
public class JwtUtil {
// 杩囨湡鏃堕棿5鍒嗛挓
private final static long EXPIRE_TIME = 5 * 60 * 1000;
/**
* 鐢熸垚绛惧悕,5min鍚庤繃鏈?
* @param username 鐢ㄦ埛鍚?
* @param secret 鐢ㄦ埛鐨勫瘑鐮?
* @return 鍔犲瘑鐨則oken
*/
public static String sign(String username, String secret) {
Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withClaim("username", username)
.withExpiresAt(expireDate)
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
/**
* 鏍¢獙token鏄惁姝g‘
* @param token 瀵嗛挜
* @param secret 鐢ㄦ埛鐨勫瘑鐮?
* @return 鏄惁姝g‘
*/
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 鑾峰緱token涓殑淇℃伅鏃犻渶secret瑙e瘑涔熻兘鑾峰緱
* @return token涓寘鍚殑鐢ㄦ埛鍚?
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
}
Spring Security 鏀归€?/h2>
鐧诲叆杩欏潡锛屾垜浠娇鐢ㄨ嚜瀹氫箟鐨?JwtAuthenticationFilter
鏉ヨ繘琛岀櫥鍏ャ€?/p>
璇锋眰閴存潈锛屾垜浠娇鐢ㄨ嚜瀹氫箟鐨?JwtAuthorizationFilter
鏉ュ鐞嗐€?/p>
涔熻澶у瑙夊緱涓や釜鍗曡瘝闀跨殑鏈夌偣鍍忥紝馃槣銆?/p>
UserDetailsServiceImpl.java
鎴戜滑棣栧厛瀹炵幇瀹樻柟鐨?UserDetailsService
鎺ュ彛锛岃繖閲屼富瑕佽礋璐d竴涓粠鏁版嵁搴撴嬁鏁版嵁鐨勬搷浣溿€?/p>
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userEntity = userService.getUserByUsername(username);
if (userEntity == null) {
throw new UsernameNotFoundException("This username didn't exist.");
}
return new User(userEntity.getUsername(), userEntity.getPassword(), userEntity.getRole());
}
}
鍚庡簭鎴戜滑杩橀渶瑕佸鍏惰繘琛岀紦瀛樻敼閫狅紝涓嶇劧姣忔璇锋眰閮借浠庢暟鎹簱鎷夸竴娆℃暟鎹壌鏉冿紝瀵规暟鎹簱鍘嬪姏澶ぇ浜嗐€?/p>
JwtAuthenticationFilter.java
杩欎釜杩囨护鍣ㄤ富瑕佸鐞嗙櫥鍏ユ搷浣滐紝鎴戜滑缁ф壙浜?UsernamePasswordAuthenticationFilter
锛岃繖鏍疯兘澶уぇ绠€鍖栨垜浠殑宸ヤ綔閲忋€?/p>
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
/*
杩囨护鍣ㄤ竴瀹氳璁剧疆 AuthenticationManager锛屾墍浠ユ澶勬垜浠繖涔堢紪鍐欙紝杩欓噷鐨?AuthenticationManager
鎴戜細浠?Security 閰嶇疆鐨勬椂鍊欎紶鍏?
*/
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
/*
杩愯鐖剁被 UsernamePasswordAuthenticationFilter 鐨勬瀯閫犳柟娉曪紝鑳藉璁剧疆姝ゆ护鍣ㄦ寚瀹?
鏂规硶涓?POST [\login]
*/
super();
setAuthenticationManager(authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 浠庤姹傜殑 POST 涓嬁鍙?username 鍜?password 涓や釜瀛楁杩涜鐧诲叆
String username = request.getParameter("username");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
// 璁剧疆涓€浜涘鎴?IP 鍟ヤ俊鎭紝鍚庨潰鎯崇敤鐨勮瘽鍙互鐢紝铏界劧娌″暐鐢?
setDetails(request, token);
// 浜ょ粰 AuthenticationManager 杩涜閴存潈
return getAuthenticationManager().authenticate(token);
}
/*
閴存潈鎴愬姛杩涜鐨勬搷浣滐紝鎴戜滑杩欓噷璁剧疆杩斿洖鍔犲瘑鍚庣殑 token
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
handleResponse(request, response, authResult, null);
}
/*
閴存潈澶辫触杩涜鐨勬搷浣滐紝鎴戜滑杩欓噷灏辫繑鍥?鐢ㄦ埛鍚嶆垨瀵嗙爜閿欒 鐨勪俊鎭?
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
handleResponse(request, response, null, failed);
}
private void handleResponse(HttpServletRequest request, HttpServletResponse response, Authentication authResult, AuthenticationException failed) throws IOException, ServletException {
ObjectMapper mapper = new ObjectMapper();
ResponseEntity responseEntity = new ResponseEntity();
response.setHeader("Content-Type", "application/json;charset=UTF-8");
if (authResult != null) {
// 澶勭悊鐧诲叆鎴愬姛璇锋眰
User user = (User) authResult.getPrincipal();
String token = JwtUtil.sign(user.getUsername(), user.getPassword());
responseEntity.setStatus(HttpStatus.OK.value());
responseEntity.setMsg("鐧诲叆鎴愬姛");
responseEntity.setData("Bearer " + token);
response.setStatus(HttpStatus.OK.value());
response.getWriter().write(mapper.writeValueAsString(responseEntity));
} else {
// 澶勭悊鐧诲叆澶辫触璇锋眰
responseEntity.setStatus(HttpStatus.BAD_REQUEST.value());
responseEntity.setMsg("鐢ㄦ埛鍚嶆垨瀵嗙爜閿欒");
responseEntity.setData(null);
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.getWriter().write(mapper.writeValueAsString(responseEntity));
}
}
}
private void handleResponse()
姝ゅ澶勭悊鐨勬柟娉曚笉鏄緢濂斤紝鎴戠殑鎯虫硶鏄烦杞埌鎺у埗鍣ㄤ腑杩涜澶勭悊锛屼絾鏄繖鏍烽壌鏉冩垚鍔熺殑 token 甯︿笉杩囧幓锛屾墍浠ュ厛杩欎箞鍐欎簡锛屾湁鐐瑰鏉傘€?/p>
JwtAuthorizationFilter.java
杩欎釜杩囨护鍣ㄥ鐞嗘瘡涓姹傞壌鏉冿紝鎴戜滑閫夋嫨缁ф壙 BasicAuthenticationFilter
锛岃€冭檻鍒?Basic 璁よ瘉鍜?JWT 姣旇緝鍍忥紝灏遍€夋嫨浜嗗畠銆?/p>
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private UserDetailsService userDetailsService;
// 浼氫粠 Spring Security 閰嶇疆鏂囦欢閭i噷浼犺繃鏉?
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserDetailsService userDetailsService) {
super(authenticationManager);
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 鍒ゆ柇鏄惁鏈?token锛屽苟涓旇繘琛岃璇?
Authentication token = getAuthentication(request);
if (token == null) {
chain.doFilter(request, response);
return;
}
// 璁よ瘉鎴愬姛
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header == null || ! header.startsWith("Bearer ")) {
return null;
}
String token = header.split(" ")[1];
String username = JwtUtil.getUsername(token);
UserDetails userDetails = null;
try {
userDetails = userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException e) {
return null;
}
if (! JwtUtil.verify(token, username, userDetails.getPassword())) {
return null;
}
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
SecurityConfiguration.java
姝ゅ鎴戜滑杩涜 Security 鐨勯厤缃紝骞朵笖瀹炵幇缂撳瓨鍔熻兘銆傜紦瀛樿繖鍧楁垜浠娇鐢ㄥ畼鏂圭幇鎴愮殑 CachingUserDetailsService
锛屽敮鐙殑缂虹偣灏辨槸瀹冩病鏈?public 鏂规硶锛屾垜浠笉鑳芥甯稿疄渚嬪寲锛岄渶瑕佹洸绾挎晳鍥斤紝涓嬮潰浠g爜涔熸湁璇︾粏璇存槑銆?/p>
// 寮€鍚?Security
@EnableWebSecurity
// 寮€鍚敞瑙i厤缃敮鎸?
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
// Spring Boot 鐨?CacheManager锛岃繖閲屾垜浠娇鐢?JCache
@Autowired
private CacheManager cacheManager;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 寮€鍚法鍩?
http.cors()
.and()
// security 榛樿 csrf 鏄紑鍚殑锛屾垜浠娇鐢ㄤ簡 token 锛岃繖涓篃娌℃湁浠€涔堝繀瑕佷簡
.csrf().disable()
.authorizeRequests()
// 榛樿鎵€鏈夎姹傞€氳繃锛屼絾鏄垜浠鍦ㄩ渶瑕佹潈闄愮殑鏂规硶鍔犱笂瀹夊叏娉ㄨВ锛岃繖鏍锋瘮鍐欐閰嶇疆鐏垫椿寰堝
.anyRequest().permitAll()
.and()
// 娣诲姞鑷繁缂栧啓鐨勪袱涓繃婊ゅ櫒
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), cachingUserDetailsService(userDetailsServiceImpl)))
// 鍓嶅悗绔垎绂绘槸 STATELESS锛屾晠 session 浣跨敤璇ョ瓥鐣?
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
// 姝ゅ閰嶇疆 AuthenticationManager锛屽苟涓斿疄鐜扮紦瀛?
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 瀵硅嚜宸辩紪鍐欑殑 UserDetailsServiceImpl 杩涗竴姝ュ寘瑁咃紝瀹炵幇缂撳瓨
CachingUserDetailsService cachingUserDetailsService = cachingUserDetailsService(userDetailsServiceImpl);
// jwt-cache 鎴戜滑鍦?ehcache.xml 閰嶇疆鏂囦欢涓湁澹版槑
UserCache userCache = new SpringCacheBasedUserCache(cacheManager.getCache("jwt-cache"));
cachingUserDetailsService.setUserCache(userCache);
/*
security 榛樿閴存潈瀹屾垚鍚庝細鎶婂瘑鐮佹姽闄わ紝浣嗘槸杩欓噷鎴戜滑浣跨敤鐢ㄦ埛鐨勫瘑鐮佹潵浣滀负 JWT 鐨勭敓鎴愬瘑閽ワ紝
濡傛灉琚姽闄や簡锛屽湪瀵?JWT 杩涜绛惧悕鐨勬椂鍊欏氨鎷夸笉鍒扮敤鎴峰瘑鐮佷簡锛屾晠姝ゅ鍏抽棴浜嗚嚜鍔ㄦ姽闄ゅ瘑鐮併€?
*/
auth.eraseCredentials(false);
auth.userDetailsService(cachingUserDetailsService);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/*
姝ゅ鎴戜滑瀹炵幇缂撳瓨鐨勬椂鍊欙紝鎴戜滑浣跨敤浜嗗畼鏂圭幇鎴愮殑 CachingUserDetailsService 锛屼絾鏄繖涓被鐨勬瀯閫犳柟娉曚笉鏄?public 鐨勶紝
鎴戜滑涓嶈兘澶熸甯稿疄渚嬪寲锛屾墍浠ュ湪杩欓噷杩涜鏇茬嚎鏁戝浗銆?
*/
private CachingUserDetailsService cachingUserDetailsService(UserDetailsServiceImpl delegate) {
Constructor<CachingUserDetailsService> ctor = null;
try {
ctor = CachingUserDetailsService.class.getDeclaredConstructor(UserDetailsService.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
Assert.notNull(ctor, "CachingUserDetailsService constructor is null");
ctor.setAccessible(true);
return BeanUtils.instantiateClass(ctor, delegate);
}
}
Ehcache 閰嶇疆
Ehcache 3 寮€濮嬶紝缁熶竴浣跨敤浜?JCache锛屽氨鏄?JSR107 鏍囧噯锛岀綉涓婂緢澶氭暀绋嬮兘鏄熀浜?Ehcache 2 鐨勶紝鎵€浠ュぇ瀹跺彲鑳藉湪鍙傜収缃戜笂鐨勬暀绋嬩細閬囧埌寰堝鍧戙€?/p>
JSR107锛歟mm锛屽叾瀹?JSR107 鏄竴绉嶇紦瀛樻爣鍑嗭紝鍚勪釜妗嗘灦鍙閬靛畧杩欎釜鏍囧噯锛屽氨鏄幇瀹炲ぇ涓€缁熴€傚樊涓嶅灏辨槸鎴戜笉闇€瑕佹洿鏀圭郴缁熶唬鐮侊紝涔熻兘闅忔剰鏇存崲搴曞眰鐨勭紦瀛樼郴缁熴€?/p>
鍦?resources 鐩綍涓嬪垱寤?ehcache.xml
鏂囦欢锛?/p>
<ehcache:config
xmlns:ehcache="http://www.ehcache.org/v3"
xmlns:jcache="http://www.ehcache.org/v3/jsr107">
<ehcache:cache alias="jwt-cache">
<!-- 鎴戜滑浣跨敤鐢ㄦ埛鍚嶄綔涓虹紦瀛樼殑 key锛屾晠浣跨敤 String -->
<ehcache:key-type>java.lang.String</ehcache:key-type>
<ehcache:value-type>org.springframework.security.core.userdetails.User</ehcache:value-type>
<ehcache:expiry>
<ehcache:ttl unit="days">1</ehcache:ttl>
</ehcache:expiry>
<!-- 缂撳瓨瀹炰綋鐨勬暟閲?-->
<ehcache:heap unit="entries">2000</ehcache:heap>
</ehcache:cache>
</ehcache:config>
鍦?application.properties
涓紑鍚紦瀛樻敮鎸侊細
spring.cache.type=jcache
spring.cache.jcache.config=classpath:ehcache.xml
缁熶竴鍏ㄥ眬寮傚父
鎴戜滑瑕佹妸寮傚父鐨勮繑鍥炲舰寮忎篃缁熶竴浜嗭紝杩欐牱鎵嶈兘鏂逛究鍓嶇鐨勮皟鐢ㄣ€?/p>
鎴戜滑骞冲父浼氫娇鐢?@RestControllerAdvice
鏉ョ粺涓€寮傚父锛屼絾鏄畠鍙兘绠$悊 Controller 灞傞潰鎶涘嚭鐨勫紓甯搞€係ecurity 涓姏鍑虹殑寮傚父涓嶄細鎶佃揪 Controller锛屾棤娉曡 @RestControllerAdvice
鎹曡幏锛屾晠鎴戜滑杩樿鏀归€?ErrorController
銆?/p>
@RestController
public class CustomErrorController implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public ResponseEntity handleError(HttpServletRequest request, HttpServletResponse response) {
return new ResponseEntity(response.getStatus(), (String) request.getAttribute("javax.servlet.error.message"), null);
}
}
娴嬭瘯
鍐欎釜鎺у埗鍣ㄨ瘯璇曪紝澶у涔熷彲浠ュ弬鑰冩垜鎺у埗鍣ㄩ噷闈㈣幏鍙栫敤鎴蜂俊鎭殑鏂瑰紡锛屾帹鑽愪娇鐢?@AuthenticationPrincipal
杩欎釜娉ㄨВ锛侊紒锛?/p>
@RestController
public class MainController {
// 浠讳綍浜洪兘鍙互璁块棶锛屽湪鏂规硶涓垽鏂敤鎴锋槸鍚﹀悎娉?
@GetMapping("everyone")
public ResponseEntity everyone() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (! (authentication instanceof AnonymousAuthenticationToken)) {
// 鐧诲叆鐢ㄦ埛
return new ResponseEntity(HttpStatus.OK.value(), "You are already login", authentication.getPrincipal());
} else {
return new ResponseEntity(HttpStatus.OK.value(), "You are anonymous", null);
}
}
@GetMapping("user")
@PreAuthorize("hasAuthority('ROLE_USER')")
public ResponseEntity user(@AuthenticationPrincipal UsernamePasswordAuthenticationToken token) {
return new ResponseEntity(HttpStatus.OK.value(), "You are user", token);
}
@GetMapping("admin")
@IsAdmin
public ResponseEntity admin(@AuthenticationPrincipal UsernamePasswordAuthenticationToken token) {
return new ResponseEntity(HttpStatus.OK.value(), "You are admin", token);
}
}
鎴戣繖閲岃繕浣跨敤浜?@IsAdmin
娉ㄨВ锛?code>@IsAdmin 娉ㄨВ濡備笅锛?/p>
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public @interface IsAdmin {
}
杩欐牱鑳界渷鍘绘瘡娆$紪鍐欎竴闀夸覆鐨?@PreAuthorize()
锛岃€屼笖鏇村姞鐩磋銆?/p>
FAQ
濡備綍瑙e喅JWT杩囨湡闂锛?/h3>
鎴戜滑鍙互鍦?JwtAuthorizationFilter
涓姞鐐规枡锛屽鏋滅敤鎴峰揩杩囨湡浜嗭紝杩斿洖涓壒鍒殑鐘舵€佺爜锛屽墠绔敹鍒版鐘舵€佺爜鍘昏闂?GET /re_authentication
鎼哄甫鑰佺殑 token 閲嶆柊鎷夸竴涓柊鐨?token 鍗冲彲銆?/p>
濡備綍浣滃簾宸查鍙戞湭杩囨湡鐨?token锛?/h3>
鎴戜釜浜虹殑鎯虫硶鏄妸姣忔鐢熸垚鐨?token 鏀惧叆缂撳瓨涓紝姣忔璇锋眰閮戒粠缂撳瓨閲屾嬁锛屽鏋滄病鏈夊垯浠h〃姝ょ紦瀛樻姤搴熴€?/p>
椤圭洰鍦板潃锛歨ttps://github.com/Smith-Cruise/Spring-Boot-Security-JWT-SPA
鏈枃棣栧彂浜庡叕浼楀彿锛欽ava鐗坵eb椤圭洰锛屾杩庡叧娉ㄨ幏鍙栨洿澶氱簿褰╁唴瀹?/p>