當前位置: 妍妍網 > 碼農

SpringBoot 動態許可權校驗,優雅的實作方案

2024-02-26碼農

推薦關註

頂級架構師後台回復 1024 有特別禮包

來源: blog.csdn.net/mofsfely2/article/details/113756506

上一篇:

大家好,我是頂級架構師。

1、背景


簡單先說一下需求吧,這樣也好讓看的人知道到底適不適合自己。

  1. 實作自訂的登入認證。

  2. 登入成功,生成token並將token 交由redis管理。

  3. 登入後對使用者存取的介面進行介面級別許可權認證。

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是基於過濾器進行安全認證的。

    我們需要自訂:

    1. 登入過濾器 :負責過濾登入請求,再交由自訂的登入認證管理器處理。

    2. 登入成功處理類 :顧名思義,登入成功後的一些處理(設定返回資訊提示「登入成功!」,返回數據型別為json)。

    3. 登入失敗處理類 :類似登入成功處理類。Ps:登入成功處理類和失敗處理類有預設的實作可以不自訂。但是建議自訂,因為返回的資訊為英文,一般情況不符合要求。

    4. 登入認證管理器 :根據過濾器傳過來的登入參數,進行登入認證,認證後授權。

    新建登入成功處理類

    需要實作 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> 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> 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、微服務、資料庫、數據結構等等。

    獲取方式:點「在看」,關註公眾號並回復 手冊 領取,更多內容陸續奉上。

    明天見(。・ω・。)