推薦關註
頂級架構師後台回復 1024 有特別禮包
來源: blog.csdn.net/mofsfely2/article/details/113756506
上一篇:
大家好,我是頂級架構師。
1、背景
簡單先說一下需求吧,這樣也好讓看的人知道到底適不適合自己。
實作自訂的登入認證。
登入成功,生成token並將token 交由redis管理。
登入後對使用者存取的介面進行介面級別許可權認證。
springSecurity提供的註解許可權校驗適合的場景是系統中僅有固定的幾個角色,且角色的憑證不可修改(如果修改需要改動程式碼)。
@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
註:ROLE_TELLER是寫死的。
後端系統的存取請求有以下幾種型別:
登入、登出(可自訂url)
匿名使用者可存取的介面(靜態資源,demo範例等)
其他介面(在登入的前提下,繼續判斷存取者是否有許可權存取)
2、環境搭建
依賴引入,包括springSecurity、redis、redis session需要的依賴:
<!--springSecurity安全框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<!-- 預設透過SESSIONId改為透過請求頭與redis配合驗證session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<!--redis支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
註:springBoot版本也是2.3.4.RELEASE,如果有版本對應問題,自行解決。有用到swagger,為了便於測試。
新建springSecurity配置類
新建 WebSecurityConfig.java 繼承自 WebSecurityConfigurerAdapter,過濾匿名使用者可存取的介面。
WebSecurityConfig作為springSecurity的主配置檔。
@Configuration
@EnableWebSecurity
public classWebSecurityConfigextendsWebSecurityConfigurerAdapter{
/**
* Swagger等靜態資源不進行攔截
*/
@Override
publicvoidconfigure(WebSecurity web){
web.ignoring().antMatchers(
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/error",
"/webjars/**",
"/resources/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v2/api-docs");
}
@Override
protectedvoidconfigure(HttpSecurity http)throws Exception {
http.authorizeRequests()
//配置一些不需要登入就可以存取的介面
.antMatchers("/demo/**", "/about/**").permitAll()
//任何尚未匹配的URL只需要使用者進行身份驗證
.anyRequest().authenticated()
.and()
.formLogin()//允許使用者進行基於表單的認證
.loginPage("/mylogin");
}
}
註:證明可以存取靜態資源不會被攔截
自訂登入認證
springSecurity是基於過濾器進行安全認證的。
我們需要自訂:
登入過濾器 :負責過濾登入請求,再交由自訂的登入認證管理器處理。
登入成功處理類 :顧名思義,登入成功後的一些處理(設定返回資訊提示「登入成功!」,返回數據型別為json)。
登入失敗處理類 :類似登入成功處理類。Ps:登入成功處理類和失敗處理類有預設的實作可以不自訂。但是建議自訂,因為返回的資訊為英文,一般情況不符合要求。
登入認證管理器 :根據過濾器傳過來的登入參數,進行登入認證,認證後授權。
新建登入成功處理類
需要實作 AuthenticationSuccessHandler
@Component
public classCustomAuthenticationSuccessHandlerimplementsAuthenticationSuccessHandler{
privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationSuccessHandler. class);
@Override
publicvoidonAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
//登入成功返回的認證體,具體格式在後面的登入認證管理器中
String responseJson = JackJsonUtil.object2String(ResponseFactory.success(authentication));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("登入成功!");
}
response.getWriter().write(responseJson);
}
}
新建登入失敗處理類
實作 AuthenticationFailureHandler
@Component
public classCustomAuthenticationFailureHandlerimplementsAuthenticationFailureHandler{
privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationFailureHandler. class);
@Override
publicvoidonAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)throws IOException {
String errorMsg;
if (StringUtils.isNotBlank(e.getMessage())) {
errorMsg = e.getMessage();
} else {
errorMsg = CodeMsgEnum.LOG_IN_FAIL.getMsg();
}
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
String responseJson = JackJsonUtil.object2String(ResponseFactory.fail(CodeMsgEnum.LOG_IN_FAIL,errorMsg));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("認證失敗!");
}
response.getWriter().write(responseJson);
}
}
新建登入認證管理器
實作 AuthenticationProvider ,負責具體的身份認證(一般資料庫認證,在登入過濾器過濾掉請求後傳入)
@Component
public classUserVerifyAuthenticationProviderimplementsAuthenticationProvider{
private PasswordEncoder passwordEncoder;
@Autowired
private UserService userService;
@Override
public Authentication authenticate(Authentication authentication)throws AuthenticationException {
String userName = (String) authentication.getPrincipal(); // Principal 主體,一般指使用者名稱
String passWord = (String) authentication.getCredentials(); //Credentials 網路憑證,一般指密碼
//透過帳號去資料庫查詢使用者以及使用者擁有的角色資訊
UserRoleVo userRoleVo = userService.findUserRoleByAccount(userName);
//資料庫密碼
String encodedPassword = userRoleVo.getPassWord();
//credentials憑證即為前端傳入密碼,因為前端一般用Base64加密過所以需要解密。
String credPassword = new String(Base64Utils.decodeFromString(passWord), StandardCharsets.UTF_8);
// 驗證密碼:前端明文,資料庫密文
passwordEncoder = new MD5Util();
if (!passwordEncoder.matches(credPassword, encodedPassword)) {
thrownew AuthenticationServiceException("帳號或密碼錯誤!");
}
//ps:GrantedAuthority對認證主題的套用層面的授權,含當前使用者的許可權資訊,通常使用角色表示
List<GrantedAuthority> roles = new LinkedList<>();
List<Role> roleList = userRoleVo.getRoleList();
roleList.forEach(role -> {
SimpleGrantedAuthority roleId = new SimpleGrantedAuthority(role.getRoleId().toString());
roles.add(roleId);
});
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, passWord, roles);
token.setDetails(userRoleVo);//這裏可以放使用者的詳細資訊
return token;
}
@Override
publicbooleansupports( class<?> authentication){
returnfalse;
}
}
新建登入過濾器
LoginFilter.java繼承
UsernamePasswordAuthenticationFilter
,負責過濾登入請求並交由登入認證管理器進行具體的認證。另外,搜尋公眾號Linux就該這樣學後台回復「猴子」,獲取一份驚喜禮包。
public classLoginFilterextendsUsernamePasswordAuthenticationFilter{
private UserVerifyAuthenticationProvider authenticationManager;
/**
* @param authenticationManager 認證管理器
* @param successHandler 認證成功處理類
* @param failureHandler 認證失敗處理類
*/
publicLoginFilter(UserVerifyAuthenticationProvider authenticationManager,
CustomAuthenticationSuccessHandler successHandler,
CustomAuthenticationFailureHandler failureHandler){
//設定認證管理器(對登入請求進行認證和授權)
this.authenticationManager = authenticationManager;
//設定認證成功後的處理類
this.setAuthenticationSuccessHandler(successHandler);
//設定認證失敗後的處理類
this.setAuthenticationFailureHandler(failureHandler);
//可以自訂登入請求的url
super.setFilterProcessesUrl("/myLogin");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {
try {
//轉換請求入參
UserDTO loginUser = new ObjectMapper().readValue(request.getInputStream(), UserDTO. class);
//入參傳入認證管理器進行認證
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUserName(), loginUser.getPassWord())
);
} catch (IOException e) {
e.printStackTrace();
returnnull;
}
}
}
最後配置到WebSecurityConfig中:
@Configuration
@EnableWebSecurity
public classWebSecurityConfigextendsWebSecurityConfigurerAdapter{
@Autowired
private UserVerifyAuthenticationProvider authenticationManager;//認證使用者類
@Autowired
private CustomAuthenticationSuccessHandler successHandler;//登入認證成功處理類
@Autowired
private CustomAuthenticationFailureHandler failureHandler;//登入認證失敗處理類
/**
* Swagger等靜態資源不進行攔截
*/
@Override
publicvoidconfigure(WebSecurity web){
web.ignoring().antMatchers(
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/error",
"/webjars/**",
"/resources/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v2/api-docs");
}
@Override
protectedvoidconfigure(HttpSecurity http)throws Exception {
http.authorizeRequests()
//配置一些不需要登入就可以存取的介面
.antMatchers("/demo/**", "/about/**").permitAll()
//任何尚未匹配的URL只需要使用者進行身份驗證
.anyRequest().authenticated()
.and()
//配置登入過濾器
.addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler))
.csrf().disable();
}
}
驗證配置
存取登入請求:
成功進入LoginFilter
安全頭和登入返回token
依賴已經引入了,設定session由redis儲存,只需要如下圖配置:
session:
store-type:redis
redis:
namespace:spring:session:admin
# session 無操作失效時間 30 分鐘
timeout:1800
設定token放入返回的header中需要在WebSecurityConfig中加入
/**
* 配置 HttpSessionIdResolver Bean
* 登入之後將會在 Response Header x-auth-token 中 返回當前 sessionToken
* 將token儲存在前端 每次呼叫的時候 Request Header x-auth-token 帶上 sessionToken
*/
@Bean
public HttpSessionIdResolver httpSessionIdResolver(){
return HeaderHttpSessionIdResolver.xAuthToken();
}
關於安全頭資訊可以參考:
https://docs.spring.io/spring-security/site/docs/5.2.1.BUILD-SNAPSHOT/reference/htmlsingle/#ns-headers
設定安全請求頭需要設定WebSecurityConfig中加入
protectedvoidconfigure(HttpSecurity http)throws Exception {
http.authorizeRequests()
//配置一些不需要登入就可以存取的介面
.antMatchers("/demo/**", "/about/**").permitAll()
//任何尚未匹配的URL只需要使用者進行身份驗證
.anyRequest().authenticated()
.and()
//配置登入過濾器
.addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler))
.csrf().disable();
//配置頭部
http.headers()
.contentTypeOptions()
.and()
.xssProtection()
.and()
//禁用緩存
.cacheControl()
.and()
.httpStrictTransportSecurity()
.and()
//禁用頁面鑲嵌frame劫持安全協定 // 防止iframe 造成跨域
.frameOptions().disable();
}
進行登入測試,驗證結果:
註:響應中有token
檢視redis。成功保存進了redis
介面許可權校驗
方式一:
如下圖,詳細請看連結。
https://blog.csdn.net/coolwindd/article/details/104640289/
註:不使用這種方式,原因在於,需要自己判斷是否匿名使用者。
方法二:
參考
https://blog.csdn.net/mapleleafforest/article/details/106637052
Spring Security使用
FilterSecurityInterceptor
過濾器來進行URL許可權校驗,實際使用流程大致如下:
正常情況的介面許可權判斷:
返回那些可以存取當前url的角色
1、定義一個
MyFilterInvocationSecurityMetadataSource
實作
FilterInvocationSecurityMetadataSource
的類,重寫getAttributes方法。
方法的作用是:返回哪些角色可以存取當前url,這個肯定是從資料庫中獲取。要註意的是對於PathVariable傳參的url,資料庫中存的是這樣的:
/getUserByName/{name}
。但實際存取的url中name是具體的值。類似的
/user/getUserById
也要可以匹配
/user/getUserById?1
。
package com.aliyu.security.provider;/**
* @author: aliyu
* @create: 2021-02-05 14:53
* @description:
*/
import com.aliyu.service.role.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
*@author: aliyu
*@create:
*@description: 第一步:資料庫查詢所有許可權出來:
* 之所以要所有許可權,因為資料庫url和實際請求url並不能直接匹配需要。比方:/user/getUserById 匹配 /user/getUserById?1
* 第二步:透過httpUrl匹配器找出允許存取當前請求的角色列表(哪些角色可以存取此請求)
*/
@Component
public classMyFilterInvocationSecurityMetadataSourceimplementsFilterInvocationSecurityMetadataSource{
@Autowired
private RoleService roleService;
/**
* 返回當前URL允許存取的角色列表
* @param object
* @return
* @throws IllegalArgumentException
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object)throws IllegalArgumentException {
//入參轉為HttpServletRequest
FilterInvocation fi = (FilterInvocation) object;
HttpServletRequest request = fi.getRequest();
//從資料庫中查詢系統所有的許可權,格式為<"許可權url","能存取url的逗號分隔的roleid">
List<Map<String, String>> allUrlRoleMap = roleService.getAllUrlRoleMap();
for (Map<String, String> urlRoleMap : allUrlRoleMap) {
String url = urlRoleMap.get("url");
String roles = urlRoleMap.get("roles");
//new AntPathRequestMatcher建立httpUrl匹配器:裏面url匹配規則已經給我們弄好了,
// 能夠支持校驗PathVariable傳參的url(例如:/getUserByName/{name})
// 也能支持 /user/getUserById 匹配 /user/getUserById?1
AntPathRequestMatcher matcher = new AntPathRequestMatcher(url);
if (matcher.matches(request)){ //當前請求與httpUrl匹配器進行匹配
return SecurityConfig.createList(roles.split(","));
}
}
returnnull;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes(){
returnnull;
}
@Override
publicbooleansupports( class<?> clazz){
return FilterInvocation. class.isAssignableFrom(clazz);
}
}
註:別人是初始化的時候載入所有許可權,一次就好了。我的是每次請求都會去重新載入系統所有許可權,好處就是不用擔心許可權修改的問題。
判斷當前使用者是否擁有存取當前url的角色
定義一個
MyAccessDecisionManager
:透過實作
AccessDecisionManager
介面自訂一個決策管理器,判斷是否有存取許可權。上一步
MyFilterInvocationSecurityMetadataSource
中返回的當前請求可以的存取角色列表會傳到這裏的decide方法裏面(如果沒有角色的話,不會進入decide方法。
正常情況你存取的url必然和某個角色關聯,如果沒有關聯就不應該可以存取)。decide方法傳了當前登入使用者擁有的角色,透過判斷使用者擁有的角色中是否有一個角色和當前url可以存取的角色匹配。如果匹配,許可權校驗透過。
package com.aliyu.security.provider;/**
* @author: aliyu
* @create: 2021-02-05 15:16
* @description:
*/
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Iterator;
/**
*@author: aliyu
*@create:
*@description: 介面許可權判斷(根據MyFilterInvocationSecurityMetadataSource獲取到的請求需要的角色
* 和當前登入人的角色進行比較)
*/
@Component
public classMyAccessDecisionManagerimplementsAccessDecisionManager{
@Override
publicvoiddecide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException {
//迴圈請求需要的角色,只要當前使用者擁有的角色中包含請求需要的角色中的一個,就算透過。
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while(iterator.hasNext()){
ConfigAttribute configAttribute = iterator.next();
String needCode = configAttribute.getAttribute();
//獲取到了登入使用者的所有角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (StringUtils.equals(authority.getAuthority(), needCode)) {
return;
}
}
}
thrownew AccessDeniedException("當前存取沒有許可權");
}
@Override
publicbooleansupports(ConfigAttribute attribute){
returnfalse;
}
@Override
publicbooleansupports( class<?> clazz){
return FilterInvocation. class.isAssignableFrom(clazz);
}
}
處理匿名使用者存取無許可權資源
1、定義一個
CustomAuthenticationEntryPoint
實作
AuthenticationEntryPoint
處理匿名使用者存取無許可權資源(可以理解為未登入的使用者存取,確實有些介面是可以不登入也能存取的,比較少,我們在
WebSecurityConfig
已經配置過了。如果多的話,需要另外考慮從資料庫中獲取,並且許可權需要加一個標誌它為匿名使用者可存取)。
package com.aliyu.security.handler;
import com.aliyu.common.util.JackJsonUtil;
import com.aliyu.entity.common.vo.ResponseFactory;
import com.aliyu.security.constant.MessageConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
importstatic com.aliyu.entity.common.exception.CodeMsgEnum.MOVED_PERMANENTLY;
/**
* 未登入重新導向處理器
* <p>
* 未登入狀態下存取需要登入的介面
*
* @author
*/
public classCustomAuthenticationEntryPointimplementsAuthenticationEntryPoint{
privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationEntryPoint. class);
@Override
publicvoidcommence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
//原來不需要登入的介面,現在需要登入了,所以叫永久移動
String message = JackJsonUtil.object2String(
ResponseFactory.fail(MOVED_PERMANENTLY, MessageConstant.NOT_LOGGED_IN)
);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("未登入重新導向!");
}
response.getWriter().write(message);
}
}
處理登陸認證過的使用者存取無許可權資源
2、定義一個
CustomAccessDeniedHandler
實作
AccessDeniedHandler
處理登陸認證過的使用者存取無許可權資源。
package com.aliyu.security.handler;
import com.aliyu.common.util.JackJsonUtil;
import com.aliyu.entity.common.exception.CodeMsgEnum;
import com.aliyu.entity.common.vo.ResponseFactory;
import com.aliyu.security.constant.MessageConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 拒絕存取處理器(登入狀態下,存取沒有許可權的方法時會進入此處理器)
*
* @author
*/
public classCustomAccessDeniedHandlerimplementsAccessDeniedHandler{
privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(CustomAccessDeniedHandler. class);
@Override
publicvoidhandle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
String message = JackJsonUtil.object2String(
ResponseFactory.fail(CodeMsgEnum.UNAUTHORIZED, MessageConstant.NO_ACCESS)
);
if(LOGGER.isDebugEnabled()){
LOGGER.debug("沒有許可權存取!");
}
response.getWriter().write(message);
}
}
配置到WebSecurityConfig上面去
@Autowired
private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回當前URL允許存取的角色列表
@Autowired
private MyAccessDecisionManager accessDecisionManager;//除登入登出外所有介面的許可權校驗
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object){
object.setAccessDecisionManager(accessDecisionManager);
object.setSecurityMetadataSource(securityMetadataSource);
return object;
}
})
//用來解決匿名使用者存取無許可權資源時的異常
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
//用來解決登陸認證過的使用者存取無許可權資源時的異常
.accessDeniedHandler(new CustomAccessDeniedHandler())
完整的Java類:
package com.aliyu.security.config;
import com.aliyu.filter.LoginFilter;
import com.aliyu.security.handler.*;
import com.aliyu.security.provider.MyAccessDecisionManager;
import com.aliyu.security.provider.MyFilterInvocationSecurityMetadataSource;
import com.aliyu.security.provider.UserVerifyAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;
@Configuration
@EnableWebSecurity
public classWebSecurityConfigextendsWebSecurityConfigurerAdapter{
@Autowired
private UserVerifyAuthenticationProvider authenticationManager;//認證使用者類
@Autowired
private CustomAuthenticationSuccessHandler successHandler;//登入認證成功處理類
@Autowired
private CustomAuthenticationFailureHandler failureHandler;//登入認證失敗處理類
@Autowired
private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回當前URL允許存取的角色列表
@Autowired
private MyAccessDecisionManager accessDecisionManager;//除登入登出外所有介面的許可權校驗
/**
* 密碼加密
* @return
*/
@Bean
@ConditionalOnMissingBean(PasswordEncoder. class)
publicPasswordEncoderpasswordEncoder() {
returnnew BCryptPasswordEncoder();
}
/**
* 配置 HttpSessionIdResolver Bean
* 登入之後將會在 Response Header x-auth-token 中 返回當前 sessionToken
* 將token儲存在前端 每次呼叫的時候 Request Header x-auth-token 帶上 sessionToken
*/
@Bean
public HttpSessionIdResolver httpSessionIdResolver(){
return HeaderHttpSessionIdResolver.xAuthToken();
}
/**
* Swagger等靜態資源不進行攔截
*/
@Override
publicvoidconfigure(WebSecurity web){
web.ignoring().antMatchers(
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/error",
"/webjars/**",
"/resources/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v2/api-docs");
}
@Override
protectedvoidconfigure(HttpSecurity http)throws Exception {
http.authorizeRequests()
//配置一些不需要登入就可以存取的介面
.antMatchers("/demo/**", "/about/**").permitAll()
//任何尚未匹配的URL只需要使用者進行身份驗證
.anyRequest().authenticated()
//登入後的介面許可權校驗
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object){
object.setAccessDecisionManager(accessDecisionManager);
object.setSecurityMetadataSource(securityMetadataSource);
return object;
}
})
.and()
//配置登出處理
.logout().logoutUrl("/logout")
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
.clearAuthentication(true)
.and()
//用來解決匿名使用者存取無許可權資源時的異常
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
//用來解決登陸認證過的使用者存取無許可權資源時的異常
.accessDeniedHandler(new CustomAccessDeniedHandler())
.and()
//配置登入過濾器
.addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler))
.csrf().disable();
//配置頭部
http.headers()
.contentTypeOptions()
.and()
.xssProtection()
.and()
//禁用緩存
.cacheControl()
.and()
.httpStrictTransportSecurity()
.and()
//禁用頁面鑲嵌frame劫持安全協定 // 防止iframe 造成跨域
.frameOptions().disable();
}
}
3、其他
測試結果我就懶得寫了,就這樣了。
特別的,我們認為如果一個介面屬於當前系統,那麽它就應該有對應可以存取的角色。這樣的介面才會被我們限制住。如果一個介面只是在當前系統定義了,而沒有指明它的角色,這樣的介面是不會被我們限制的。
註意點
下面的程式碼,本意是想配置一些不需要登入也可以存取的介面。
但是測試的時候發現,任何介面的呼叫都會進入這裏
MyFilterInvocationSecurityMetadataSource getAttriButes
方法,包括我
webSecurityConfig
裏配置的不需要登入的url。結果就是不需要登入的url和沒有配置角色的介面許可權一樣待遇,要麽都能存取,要麽都不能存取!!!
所以如上圖,我在這裏配置了不需要登入的介面(因為不知道如何從
webSercurityConfig
中獲取,幹脆就配置在這裏了),去掉了
webSercurityConfig
中的相應配置。
歡迎大家進行觀點的探討和碰撞,各抒己見。如果你有疑問,也可以找我溝通和交流。 擴充套件:
為了跟上AI時代我幹了一件事兒,我建立了一個知識星球社群:ChartGPT與副業。想帶著大家一起探索 ChatGPT和新的AI時代 。
有很多小夥伴搞不定ChatGPT帳號,於是我們決定,凡是這三天之內加入ChatPGT的小夥伴,我們直接送一個正常可用的永久ChatGPT獨立帳戶。
不光是增長速度最快,我們的星球品質也絕對經得起考驗,短短一個月時間,我們的課程團隊釋出了 8個專欄、18個副業計畫 :
簡單說下這個星球能給大家提供什麽:
1、不斷分享如何使用ChatGPT來完成各種任務,讓你更高效地使用ChatGPT,以及副業思考、變現思路、創業案例、落地案例分享。
2、分享ChatGPT的使用方法、最新資訊、商業價值。
3、探討未來關於ChatGPT的機遇,共同成長。
4、幫助大家解決ChatGPT遇到的問題。
5、 提供一整年的售後服務,一起搞副業
星球福利:
1、加入星球4天後,就送ChatGPT獨立帳號。
2、邀請你加入ChatGPT會員交流群。
3、贈送一份完整的ChatGPT手冊和66個ChatGPT副業賺錢手冊。
其它福利還在籌劃中... 不過,我給你大家保證,加入星球後,收獲的價值會遠遠大於今天加入的門票費用 !
本星球第一期原價 399 ,目前屬於試營運,早鳥價 139 ,每超過50人漲價10元,星球馬上要來一波大的漲價,如果你還在猶豫,可能最後就要以 更高價格加入了 。。
早就是優勢。 建議大家盡早以便宜的價格加入!
最後給讀者整理了一份 BAT 大廠面試真題,需要的可掃碼回復「 面試題 」即可獲取。
公眾號後台回復 架構 或者 架構整潔 有驚喜禮包!
頂級架構師交流群
「頂級架構師」建立了讀者架構師交流群,大家可以添加小編微信進行加群。歡迎有想法、樂於分享的朋友們一起交流學習。
掃描添加好友邀你進架構師群,加我時註明
【
姓名+公司+職位】
版權申明:內容來源網路,版權歸原作者所有。如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝。
猜你還想看
最近面試BAT,整理一份面試資料【Java面試BAT通關手冊】,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。
獲取方式:點「在看」,關註公眾號並回復 手冊 領取,更多內容陸續奉上。
明天見(。・ω・。)