Spring?Boot如何接入Security權(quán)限認(rèn)證服務(wù)
SpringBoot實(shí)戰(zhàn):Spring Boot接入Security權(quán)限認(rèn)證服務(wù)
引言
Spring Security
是一個(gè)功能強(qiáng)大且高度可定制的身份驗(yàn)證和訪(fǎng)問(wèn)控制的框架,提供了完善的認(rèn)證機(jī)制和方法級(jí)的授權(quán)功能,是一個(gè)非常優(yōu)秀的權(quán)限管理框架。其核心是一組過(guò)濾器鏈,不同的功能經(jīng)由不同的過(guò)濾器。本文將通過(guò)一個(gè)案例將 Spring Security
整合到 SpringBoot
中,要實(shí)現(xiàn)的功能就是在認(rèn)證服務(wù)器上登錄,然后獲取Token,再訪(fǎng)問(wèn)資源服務(wù)器中的資源。
一、基本介紹
登錄驗(yàn)證:
通過(guò) JWT
為每個(gè)用戶(hù)生成一個(gè)唯一且有期限的 Token
,用戶(hù)每次請(qǐng)求都會(huì)重新生成過(guò)期時(shí)間,在規(guī)定的時(shí)間內(nèi),用戶(hù)未進(jìn)行操作 Token
就會(huì)過(guò)期,當(dāng)用戶(hù)再次請(qǐng)求時(shí)則會(huì)再次執(zhí)行登錄流程,而 Token
的過(guò)期時(shí)間應(yīng)根據(jù)實(shí)際的業(yè)務(wù)場(chǎng)景規(guī)定。
權(quán)限認(rèn)證:
權(quán)限認(rèn)證通過(guò)Spring Security
框架來(lái)實(shí)現(xiàn),在用戶(hù)成功登錄之后,當(dāng)嘗試訪(fǎng)問(wèn)系統(tǒng)資源時(shí)(即發(fā)起接口調(diào)用),服務(wù)端會(huì)根據(jù)用戶(hù)所屬的角色來(lái)判斷其是否具備相應(yīng)的訪(fǎng)問(wèn)權(quán)限。若用戶(hù)未獲得該資源的訪(fǎng)問(wèn)權(quán)限,則服務(wù)端應(yīng)當(dāng)返回明確的權(quán)限不足提示信息,以確保系統(tǒng)的安全性與用戶(hù)體驗(yàn)。
通過(guò)如圖來(lái)講解我們的實(shí)現(xiàn)目標(biāo):登錄驗(yàn)證
和 權(quán)限認(rèn)證
二、環(huán)境準(zhǔn)備
創(chuàng)建 auth_user
系統(tǒng)用戶(hù)表,并準(zhǔn)備測(cè)試數(shù)據(jù)。
CREATE TABLE `auth_user` ( `id` varchar(36) NOT NULL, `username` varchar(100) DEFAULT NULL, `password` varchar(100) DEFAULT NULL, `role` varchar(100) DEFAULT NULL, `account_non_expired` int(11) DEFAULT '0', `account_non_locked` int(11) DEFAULT '0', `credentials_non_expired` int(11) DEFAULT '0', `is_enabled` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf32; INSERT INTO auth_user (id, username, password, `role`, account_non_expired, account_non_locked, credentials_non_expired, is_enabled) VALUES ('1', 'user', '15tT+y0b+lJq2HIKUjsvvg==', 'USER', 1, 1, 1, 1), ('2', 'admin', '15tT+y0b+lJq2HIKUjsvvg==', 'ADMIN', 1, 1, 1, 1);
三、登錄代碼實(shí)現(xiàn)
1.為項(xiàng)目導(dǎo)入相關(guān)依賴(lài)
在pom.xml
文件中到入依賴(lài),除了 Security
之外 還引入了 AES
和 JWT
相關(guān)依賴(lài)
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- AES加密 --> <dependency> <groupId>org.apache.directory.studio</groupId> <artifactId>org.apache.commons.codec</artifactId> <version>1.8</version> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> </dependencies>
創(chuàng)建項(xiàng)目所需實(shí)體類(lèi):
在工程中創(chuàng)建一個(gè)新的實(shí)體類(lèi)AuthUser
,該實(shí)體類(lèi)需要實(shí)現(xiàn)Spring Security
的UserDetails
接口,并特別地,需要重寫(xiě)getAuthorities()
方法來(lái)從數(shù)據(jù)庫(kù)中動(dòng)態(tài)讀取并設(shè)置用戶(hù)的角色權(quán)限。此外,為了確保用戶(hù)賬戶(hù)處于正常激活狀態(tài),isAccountNonExpired()
、isAccountNonLocked()
、isCredentialsNonExpired()
、isEnabled()
這四個(gè)方法也必須被重寫(xiě),并且應(yīng)該基于數(shù)據(jù)庫(kù)查詢(xún)的結(jié)果或業(yè)務(wù)邏輯,無(wú)條件地返回true(假設(shè)在這個(gè)場(chǎng)景下,所有用戶(hù)賬戶(hù)都被視為有效、未過(guò)期、未鎖定且憑據(jù)未過(guò)期)。
這樣的設(shè)計(jì)確保了AuthUser
類(lèi)能夠準(zhǔn)確地反映用戶(hù)的安全狀態(tài)和權(quán)限信息,同時(shí)允許Spring Security
框架利用這些信息進(jìn)行訪(fǎng)問(wèn)控制。通過(guò)從數(shù)據(jù)庫(kù)動(dòng)態(tài)加載權(quán)限信息,系統(tǒng)能夠靈活地適應(yīng)不同用戶(hù)的權(quán)限需求,提升系統(tǒng)的安全性和靈活性。
public class AuthUser implements Serializable, UserDetails { private static final long serialVersionUID = 1L; private String id; private String username; private String password; private String role; private Integer accountNonExpired; private Integer accountNonLocked; private Integer credentialsNonExpired; private Integer isEnabled; @Override public Collection<? extends GrantedAuthority> getAuthorities() { // 獲取用戶(hù)所有權(quán)限 String[] roles = role.split(","); // 遍歷 roles,取出每一個(gè)權(quán)限進(jìn)行認(rèn)證,添加到簡(jiǎn)單的授予認(rèn)證類(lèi) List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (String role : roles) { authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); } // 返回到已經(jīng)被授予認(rèn)證的權(quán)限集合, 這里面的角色所擁有的權(quán)限都已經(jīng)被 spring security 所知道 return authorities; } @Override public boolean isAccountNonExpired() { return this.accountNonExpired != null && this.accountNonExpired == 1; } @Override public boolean isAccountNonLocked() { return this.accountNonLocked != null && this.accountNonLocked == 1; } @Override public boolean isCredentialsNonExpired() { return this.credentialsNonExpired != null && this.credentialsNonExpired == 1; } @Override public boolean isEnabled() { return this.isEnabled != null && this.isEnabled == 1; } // 略去其它 Get、Set 方法 }
創(chuàng)建 Service 服務(wù)
創(chuàng)建名為 AuthUserService
的接口,并實(shí)現(xiàn) UserDetailsService
類(lèi),重寫(xiě) loadUserByUsername()
方法( Security
認(rèn)證登錄調(diào)用的接口)。
public interface AuthUserService extends UserDetailsService { } @Service("authUserService") public class AuthUserServiceImpl implements AuthUserService { @Resource private AuthUserDao authUserDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { AuthUser authUser = authUserDao.queryByName(username); if (authUser == null) { throw new IllegalArgumentException("User [" + username + "] doesn't exist."); } return authUser; } }
AutUserDao
是用來(lái)解讀數(shù)據(jù)庫(kù)信息的類(lèi), queryByName()
是通過(guò) username
從 auth_user
數(shù)據(jù)表進(jìn)行精準(zhǔn)查詢(xún)。
Congtroller 層方法
創(chuàng)建兩個(gè)接口分別供不同角色測(cè)試。
@RestController @RequestMapping("api/resource") public class ResourceController { @GetMapping("user") public String demo1() { return "User demo."; } @GetMapping("admin") public String demo2() { return "Admin demo."; } }
四、工具類(lèi)
AES加密
在前后端數(shù)據(jù)傳輸過(guò)程中明文密碼傳輸存在相當(dāng)大的隱患,可以采用加密的方式,對(duì)信息進(jìn)行隱藏,話(huà)不多說(shuō)上代碼。
public class AESUtil { private final static String ALGORITHM = "AES/CBC/NoPadding"; private final static String DEFAULT_IV = "1234567890123456"; private final static String DEFAULT_KEY = "1234567890123456"; public static String encrypt(String data) throws Exception { return encrypt(data, DEFAULT_KEY, DEFAULT_IV); } public static String desEncrypt(String data) throws Exception { return desEncrypt(data, DEFAULT_KEY, DEFAULT_IV); } public static String encrypt(String data, String key, String iv) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM); int blockSize = cipher.getBlockSize(); byte[] dataBytes = data.getBytes(); int length = dataBytes.length; if (length % blockSize != 0) { length = length + (blockSize - (length % blockSize)); } byte[] plaintext = new byte[length]; System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length); SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES"); IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes()); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encrypted = cipher.doFinal(plaintext); return new Base64().encodeToString(encrypted); } public static String desEncrypt(String data, String key, String iv) throws Exception { byte[] encrypted1 = new Base64().decode(data); Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES"); IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes()); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); byte[] bytes = cipher.doFinal(encrypted1); return new String(bytes); } }
JWT生成
通過(guò)引入JWT
(JSON Web Tokens),我們可以高效地管理用戶(hù)的登錄狀態(tài)。JWT
能夠生成一串包含過(guò)期時(shí)間的Token
值,該值以字符串形式存在。當(dāng)Token
達(dá)到其設(shè)定的過(guò)期時(shí)間時(shí),嘗試對(duì)其進(jìn)行解析將會(huì)觸發(fā)ExpiredJwtException
異常。通過(guò)捕獲這個(gè)ExpiredJwtException
異常,我們能夠有效地判斷用戶(hù)的登錄狀態(tài)是否已經(jīng)過(guò)期。在上述描述中,createJWT()
函數(shù)負(fù)責(zé)生成Token,而parseJWT()
函數(shù)則負(fù)責(zé)解析Token
。這樣的機(jī)制既方便了Token的生成與管理,也簡(jiǎn)化了用戶(hù)登錄狀態(tài)的驗(yàn)證過(guò)程。
public class TokenUtil { /** * 密鑰 */ public static final String JWT_KEY = "ibudai"; /** * 過(guò)期時(shí)間 */ public static final Long JWT_TTL = TimeUnit.MINUTES.toMillis(5); /** * 生成 Token */ public static String createJWT(String data, Long ttlMillis) { String uuid = UUID.randomUUID().toString().replaceAll("-", ""); JwtBuilder builder = getJwtBuilder(data, ttlMillis, uuid); return builder.compact(); } /** * 解析 Token */ public static Claims parseJWT(String token) { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); } /** * 生成加密后的秘鑰 */ private static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JWT_KEY); return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); } private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { SignatureAlgorithm algorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if (ttlMillis == null) { ttlMillis = JWT_TTL; } long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) // 計(jì)算內(nèi)容 .setSubject(subject) // 簽發(fā)者 .setIssuer("budai") // 簽發(fā)時(shí)間 .setIssuedAt(now) // 加密算法簽名 .signWith(algorithm, secretKey) .setExpiration(expDate); } }
五、權(quán)限配置
接下來(lái)正式配置 Security
權(quán)限模塊。
新建SecurityConfig
類(lèi),并使其繼承自WebSecurityConfigurerAdapter
,隨后在該類(lèi)中重寫(xiě)configure(AuthenticationManagerBuilder auth)
方法。在這個(gè)方法內(nèi)部,我們將利用AuthUserService
(即之前創(chuàng)建的用于從數(shù)據(jù)庫(kù)中讀取用戶(hù)角色數(shù)據(jù)的類(lèi))來(lái)配置用戶(hù)認(rèn)證信息。這樣的配置確保了Spring Security
能夠基于數(shù)據(jù)庫(kù)中存儲(chǔ)的用戶(hù)和角色信息來(lái)執(zhí)行身份驗(yàn)證。
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthUserService authUserService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 動(dòng)態(tài)讀取數(shù)據(jù)庫(kù)信息 auth.userDetailsService(authUserService) // 自定義 AES 方式加密 .passwordEncoder(new AESEncoder()); } }
配置好上述代碼,首先來(lái)手動(dòng)配置兩個(gè)角色 budia
, admian
以及相應(yīng)的角色權(quán)限和密碼。
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 手動(dòng)配置 auth.inMemoryAuthentication() .withUser("budai").password("123456").roles("USER") .and() .withUser("admin").password("123456").roles("ADMIN", "USER") .and() // 自定義賬號(hào)信息解析方式 .passwordEncoder(new AESEncoder()); }
自定義加密
Security
中默認(rèn)提供了強(qiáng)哈希加密方式 BCryptPasswordEncoder
,但也可根據(jù)實(shí)際需求自定義加密邏輯,這通過(guò)實(shí)現(xiàn) PasswordEncoder
接口并重寫(xiě)其方法來(lái)完成。在自定義的 PasswordEncoder
實(shí)現(xiàn)中,matches
方法的 charSequence
參數(shù)實(shí)際上是用戶(hù)登錄時(shí)傳入的密碼(明文),該密碼在驗(yàn)證前可能已經(jīng)過(guò)解密處理(如果前端使用了AES等加密方式)。而 matches
方法的另一個(gè)參數(shù) s(或根據(jù)具體實(shí)現(xiàn)可能命名為其他變量),則是從數(shù)據(jù)庫(kù)中讀取的、已經(jīng)加密存儲(chǔ)的用戶(hù)密碼值。由于前端工程中實(shí)施了AES數(shù)據(jù)加密,因此在服務(wù)器端進(jìn)行密碼驗(yàn)證之前,需要先對(duì)接收到的加密密碼進(jìn)行解密操作。
public class AESEncoder implements PasswordEncoder { @Override public String encode(CharSequence charSequence) { String str = charSequence.toString(); try { String plain; if (!Objects.equals(str, "userNotFoundPassword")) { plain = AESUtil.desEncrypt(str); } else { plain = str; } return AESUtil.encrypt(plain); } catch (Exception e) { throw new RuntimeException(e); } } @Override public boolean matches(CharSequence charSequence, String s) { try { String plain = AESUtil.desEncrypt(charSequence.toString()); String result = AESUtil.encrypt(plain); return Objects.equals(result, s); } catch (Exception e) { throw new RuntimeException(e); } } }
權(quán)限分配
完成用戶(hù)角色的創(chuàng)建之后,接下來(lái)的步驟是為不同的角色分配相應(yīng)的資源權(quán)限。這通常在SecurityConfig
類(lèi)中通過(guò)重寫(xiě)configure(HttpSecurity http)
方法來(lái)實(shí)現(xiàn)。在該方法中,可以配置哪些接口(如freeAPI
、userAPI
和adminAPI
)可以被特定用戶(hù)角色訪(fǎng)問(wèn)。這些接口的配置信息可以存儲(chǔ)在yml
文件中,并通過(guò)Spring
的注解機(jī)制動(dòng)態(tài)獲取。
當(dāng)未認(rèn)證用戶(hù)嘗試訪(fǎng)問(wèn)受保護(hù)的資源時(shí),Spring Security
會(huì)自動(dòng)將請(qǐng)求重定向到登錄頁(yè)面,但在這里,我們通過(guò)formLogin().loginProcessingUrl("/api/auth/verify")
指定了一個(gè)自定義的登錄接口地址/api/auth/verify,以支持通過(guò)API請(qǐng)求方式進(jìn)行用戶(hù)認(rèn)證。用戶(hù)提交登錄請(qǐng)求后,AuthUserService
中的loadUserByUsername()
方法將被調(diào)用,以驗(yàn)證用戶(hù)的用戶(hù)名和密碼,并確定其角色。
對(duì)于認(rèn)證成功、認(rèn)證失敗以及無(wú)權(quán)限訪(fǎng)問(wèn)的情況,我們采用了匿名函數(shù)(或Lambda
表達(dá)式,具體取決于實(shí)現(xiàn)方式)來(lái)處理這些事件的邏輯。這些處理邏輯可能包括重定向到特定頁(yè)面、返回錯(cuò)誤信息或執(zhí)行其他自定義操作。
public class AESEncoder implements PasswordEncoder { @Override public String encode(CharSequence charSequence) { String str = charSequence.toString(); try { String plain; if (!Objects.equals(str, "userNotFoundPassword")) { plain = AESUtil.desEncrypt(str); } else { plain = str; } return AESUtil.encrypt(plain); } catch (Exception e) { throw new RuntimeException(e); } } @Override public boolean matches(CharSequence charSequence, String s) { try { String plain = AESUtil.desEncrypt(charSequence.toString()); String result = AESUtil.encrypt(plain); return Objects.equals(result, s); } catch (Exception e) { throw new RuntimeException(e); } } }
六、邏輯處理
成功處理
用戶(hù)成功通過(guò)認(rèn)證后,系統(tǒng)會(huì)執(zhí)行兩個(gè)關(guān)鍵步驟來(lái)管理登錄狀態(tài)和權(quán)限控制。首先,會(huì)生成一個(gè)JWT
(JSON Web Token)Token
值,該Token
用于后續(xù)請(qǐng)求的登錄狀態(tài)管理。JWT是基于登錄用戶(hù)的用戶(hù)名、密碼(通常是密碼的哈希值,而非明文)及角色信息序列化后的JSON數(shù)據(jù)計(jì)算得出的,確保了數(shù)據(jù)的安全性和可驗(yàn)證性。其次,用戶(hù)的角色信息會(huì)被封裝成一個(gè)Authentication
認(rèn)證碼,該認(rèn)證碼是username:password
(注意:這里的password
部分應(yīng)替換為更安全的信息,如用戶(hù)ID或角色的哈希值,因?yàn)橹苯影艽a是不安全的)經(jīng)過(guò)Base64
編碼后的值,用于后續(xù)的權(quán)限過(guò)濾。
這兩個(gè)認(rèn)證信息——JWT Token
和Authentication
認(rèn)證碼——都會(huì)通過(guò)HTTP
響應(yīng)的請(qǐng)求頭返回給前端。前端接收到這些信息后,會(huì)將其存儲(chǔ)起來(lái),并在后續(xù)發(fā)出的所有請(qǐng)求中,在請(qǐng)求頭中攜帶這兩個(gè)參數(shù)。后端則通過(guò)配置過(guò)濾器與Spring Security框架,實(shí)現(xiàn)對(duì)這些請(qǐng)求頭的解析,從而驗(yàn)證用戶(hù)的登錄狀態(tài)和訪(fǎng)問(wèn)權(quán)限,完成登錄狀態(tài)的管理與權(quán)限訪(fǎng)問(wèn)控制。
失敗處理
用戶(hù)未通過(guò) Security 認(rèn)證時(shí),需要通過(guò)驗(yàn)證碼狀態(tài)等信息來(lái)響應(yīng)給前端, 在這里我們通過(guò)新建的返回類(lèi)? 來(lái)返回結(jié)果給前端。
private void failureHandle(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { String msg; if (exception instanceof LockedException) { msg = "Account has been locked, please contact the administrator."; } else if (exception instanceof BadCredentialsException) { msg = "Account credential error, please recheck."; } else { msg = "Account doesn't exist, please recheck."; } response.setContentType("application/json;charset=UTF-8"); response.setStatus(203); ResultData<Object> result = new ResultData<>(203, msg, null); response.getWriter().write(objectMapper.writeValueAsString(result)); }
無(wú)權(quán)攔截
在用戶(hù)沒(méi)有經(jīng)過(guò) 權(quán)限認(rèn)證的情況下訪(fǎng)問(wèn)資源,則需要進(jìn)行攔截并返回響應(yīng)的狀態(tài)信息。
private void unAuthHandle(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { String msg = "Please login and try again."; response.setContentType("application/json;charset=UTF-8"); response.setStatus(203); ResultData<Object> result = new ResultData<>(203, msg, null); response.getWriter().write(objectMapper.writeValueAsString(result)); }
七、Filter配置
Bean注入
@Configuration public class FilterConfig { /** * 設(shè)置放行資源 * * 例:/api/auth/verify */ @Value("${auth.api.verify}") private String verifyAPI; @Bean public FilterRegistrationBean<AuthFilter> orderFilter1() { FilterRegistrationBean<AuthFilter> filter = new FilterRegistrationBean<>(); filter.setName("auth-filter"); // Set effect url filter.setUrlPatterns(Collections.singleton("/**")); // Set ignore url, when multiply the value spilt with "," filter.addInitParameter("excludedUris", verifyAPI); filter.setOrder(-1); filter.setFilter(new AuthFilter()); return filter; } }
攔截邏輯
我們新建一個(gè)名為AuthFilter
的自定義過(guò)濾器類(lèi)并實(shí)現(xiàn)Filter
接口時(shí),我們需要重點(diǎn)關(guān)注doFilter()
方法的實(shí)現(xiàn)。如之前所述,一旦用戶(hù)通過(guò)登錄認(rèn)證成功,系統(tǒng)會(huì)將JWT Token
和Authentication
認(rèn)證信息寫(xiě)入HTTP響應(yīng)的請(qǐng)求頭中,并返回給前端。之后,前端在發(fā)起任何需要認(rèn)證或權(quán)限驗(yàn)證的請(qǐng)求時(shí),都應(yīng)在請(qǐng)求頭中包含這兩個(gè)參數(shù)。
在請(qǐng)求到達(dá)后端時(shí),首先會(huì)觸發(fā)Spring Security
的認(rèn)證流程。Spring Security
會(huì)使用請(qǐng)求頭中的Authentication
認(rèn)證信息(盡管通常不直接使用username:password
格式的Base64編碼,而是可能使用更安全的認(rèn)證令牌,如預(yù)共享密鑰生成的Token或基于HTTP頭部的認(rèn)證方式)進(jìn)行初步的身份驗(yàn)證。這一部分是Spring Security
內(nèi)部自動(dòng)處理的,我們無(wú)需直接操作。
一旦通過(guò)Spring Security
的身份驗(yàn)證,請(qǐng)求將繼續(xù)流向我們配置的AuthFilter
。在AuthFilter
的doFilter()
方法中,我們需要編寫(xiě)邏輯來(lái)解析請(qǐng)求頭中的JWT Token。這個(gè)Token包含了用戶(hù)的會(huì)話(huà)信息,如用戶(hù)名、角色以及Token的簽發(fā)和過(guò)期時(shí)間等。我們將驗(yàn)證這個(gè)Token是否有效(比如檢查它是否未過(guò)期),如果Token已過(guò)期,我們需要構(gòu)造一個(gè)包含相應(yīng)錯(cuò)誤信息的響應(yīng),并通過(guò)HTTP狀態(tài)碼(如401 Unauthorized)返回給前端。前端接收到這個(gè)響應(yīng)后,可以根據(jù)需要重定向用戶(hù)到登錄頁(yè)面。
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; int status; String msg; String token = req.getHeader("Token"); if (StringUtils.isNotBlank(token)) { boolean isExpired = false; try { TokenUtil.parseJWT(token); } catch (ExpiredJwtException e) { isExpired = true; } if (!isExpired) { filterChain.doFilter(req, servletResponse); return; } else { status = 203; msg = "Login expired."; } } else { status = 203; msg = "Please login and try again."; } response.setContentType("application/json;charset=UTF-8"); response.setStatus(status); ResultData<Object> result = new ResultData<>(status, msg, null); response.getWriter().write(objectMapper.writeValueAsString(result)); }
八、跨域處理
在工程中新建 CorsConfig
類(lèi)實(shí)現(xiàn) WebMvcConfigurer
接口并重寫(xiě) addCorsMappings()
方法配置跨域信息
@Configuration
public class CorsConfig implements WebMvcConfigurer {
/** * 設(shè)置跨域訪(fǎng)問(wèn)地址,逗號(hào)分隔 * * 例:http://localhost:8080,http://127.0.0.1:8080 */ @Value("${auth.host.cors}") private String hosts; @Override public void addCorsMappings(CorsRegistry registry) { String[] crosHost = hosts.trim().split(","); // 設(shè)置允許跨域的路徑 registry.addMapping("/**") // 設(shè)置允許跨域請(qǐng)求的域名 .allowedOriginPatterns(crosHost) // 是否允許cookie .allowCredentials(true) // 設(shè)置允許的請(qǐng)求方式 .allowedMethods("GET", "POST", "DELETE", "PUT") // 設(shè)置允許的header屬性 .allowedHeaders("*") // 跨域允許時(shí)間 .maxAge(TimeUnit.SECONDS.toMillis(5)); } }
到此這篇關(guān)于Spring Boot接入Security權(quán)限認(rèn)證服務(wù)的文章就介紹到這了,更多相關(guān)Spring Boot接入Security權(quán)限認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis-generator-gui根據(jù)需求改動(dòng)示例
這篇文章主要為大家介紹了mybatis-generator-gui根據(jù)需求改動(dòng)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09SpringBoot兩種方式接入DeepSeek的實(shí)現(xiàn)
本文主要介紹了SpringBoot兩種方式接入DeepSeek的實(shí)現(xiàn),包括HttpClient方式和基于spring-ai-openai的方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03Springmvc 4.x利用@ResponseBody返回Json數(shù)據(jù)的方法
這篇文章主要介紹了Springmvc 4.x利用@ResponseBody返回Json數(shù)據(jù)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04