欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?Security+JWT簡述(附源碼)

 更新時間:2023年04月17日 10:12:03   作者:IT自習(xí)小空間  
SpringSecurity是一個強(qiáng)大的可高度定制的認(rèn)證和授權(quán)框架,對于Spring應(yīng)用來說它是一套Web安全標(biāo)準(zhǔn),下面這篇文章主要給大家介紹了關(guān)于Spring?Security+JWT簡述的相關(guān)資料,需要的朋友可以參考下

一. 什么是Spring Security

Spring Security是Spring家族的一個安全管理框架, 相比于另一個安全框架Shiro, 它具有更豐富的功能。一般中大型項目都是使用SpringSecurity做安全框架, 而Shiro上手比較簡單

spring security 的核心功能:

  • 認(rèn)證(你是誰): 只有你的用戶名或密碼正確才能訪問某些資源
  • 授權(quán)(你能干嘛): 當(dāng)前用戶具有哪些功能, 將資源進(jìn)行劃分, 如在公司中分為普通資料和高級資料, 只有經(jīng)理用戶以上才能訪文高級資料, 其他人只能擁有訪問普通資料的權(quán)限。

1. 登陸校驗的流程

2. SpringSecurity基礎(chǔ)案例

首先創(chuàng)建一個Springboot的項目

添加依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

創(chuàng)建一個controller類

@RestController
public class TestController {

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

啟動項目訪問http://localhost:8080/login, 發(fā)現(xiàn)頁面并沒有hello字符, 下圖是SpringSeurity默認(rèn)的登陸界面, 默認(rèn)用戶名為user, 密碼為啟動項目時在輸出框中的內(nèi)容

在實際項目中, 顯然不能使用默認(rèn)的登陸界面, 所以我們需要自定義登陸認(rèn)證和授權(quán)

二. Spring Security原理流程

SpringSecurity底層實現(xiàn)是一系列過濾器鏈

默認(rèn)自動配置的過濾器

過濾器作用
WebAsyncManagerIntegrationFilter將WebAsyncManger與SpringSecurity上下文進(jìn)行集成
SecurityContextPersistenceFilter在處理請求之前, 將安全信息加載到SecurityContextHolder中
HeaderWriterFilter處理頭信息假如響應(yīng)中
CsrfFilter處理CSRF攻擊
LogoutFilter處理注銷登錄
UsernamePasswordAuthenticationFilter處理表單登錄
DefaultLoginPageGeneratingFilter配置默認(rèn)登錄頁面
DefaultLogoutPageGeneratingFilter配置默認(rèn)注銷頁面
BasicAuthenticationFilter處理HttpBasic登錄
RequestCacheAwareFilter處理請求緩存
SecurityContextHolderAwareRequestFilter包裝原始請求
AnonymousAuthenticationFilter配置匿名認(rèn)證
SessionManagementFilter處理session并發(fā)問題
ExceptionTranslationFilter處理認(rèn)證/授權(quán)中的異常
FilterSecurityInterceptor處理授權(quán)相關(guān)

下圖是主要的過濾器

上圖只畫出了核心的過濾器

UsernamePasswordAuthenticationFilter: 負(fù)責(zé)處理登陸頁面填寫的用戶名和密碼的登陸請求

ExceptionTranslationFilter: 處理過濾器鏈中拋出的任何AccessDeniedException和AuthenticationException異常

FilterSecurityInterceptor: 負(fù)責(zé)權(quán)限校驗的過濾器

1. 大致流程

(1) 下面是UsernamePasswordAuthenticationFilter中的attemptAuthentication方法, 該方法會將前端發(fā)送的用戶名和密碼封裝為UsernamePasswordAuthenticationToken對象, 該對象是Authentication對象的實現(xiàn)類

注意: attemptAuthentication方法主要處理視圖表單認(rèn)證, 現(xiàn)今都是前后端分離項目導(dǎo)致不能使用該方法進(jìn)行攔截, 所以我們需要自己實現(xiàn)一個過濾器覆蓋或者在UsernamePasswordAuthenticationFilter之前做用戶名和密碼攔截處理.

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    } else {
        String username = this.obtainUsername(request);
        String password = this.obtainPassword(request);
        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

(2) 返回getAuthenticationManager.authenticate(authRequest), 將未認(rèn)證的Authentication對象傳入AuthenticationManager , 進(jìn)入authenticate方法我們看到AuthenticationManager是一個接口, 該接口主要做認(rèn)證管理, 它的默認(rèn)實現(xiàn)類是ProviderManager

public interface AuthenticationManager {
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}

(3) 在SpringSecurity中, 在項目中支持多種不同方式的認(rèn)證方式, 不同的認(rèn)證方式對應(yīng)不同的AuthenticationProvider, 多個AuthenticationProvider 組成一個列表, 這個列表由ProviderManager代理, 在ProviderManager中遍歷列表中的每一個AuthenticationProvider進(jìn)行認(rèn)證

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    boolean debug = logger.isDebugEnabled();
    // 迭代遍歷認(rèn)證列表
    Iterator var8 = this.getProviders().iterator();

    while(var8.hasNext()) {
    	// 取出當(dāng)前認(rèn)證
        AuthenticationProvider provider = (AuthenticationProvider)var8.next();
        // 當(dāng)前認(rèn)證是否支持當(dāng)前的用戶名和密碼信息
        if (provider.supports(toTest)) {
            if (debug) {
                logger.debug("Authentication attempt using " + provider.getClass().getName());
            }

            try {
            	// 開始做認(rèn)證處理
                result = provider.authenticate(authentication);
                if (result != null) {
                	// 認(rèn)證成功時候返回
                    this.copyDetails(authentication, result);
                    break;
                }
            } catch (InternalAuthenticationServiceException | AccountStatusException var13) {
                this.prepareException(var13, authentication);
                throw var13;
            } catch (AuthenticationException var14) {
                lastException = var14;
            }
        }
    }

	// 不支持當(dāng)前認(rèn)證并且parent支持該認(rèn)證
    if (result == null && this.parent != null) {
        try {
            result = parentResult = this.parent.authenticate(authentication);
        } catch (ProviderNotFoundException var11) {
        } catch (AuthenticationException var12) {
            parentException = var12;
            lastException = var12;
        }
    }

    if (result != null) {
        if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
            ((CredentialsContainer)result).eraseCredentials();
        }

        if (parentResult == null) {
            this.eventPublisher.publishAuthenticationSuccess(result);
        }

        return result;
    } else {
        if (lastException == null) {
            lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
        }

        if (parentException == null) {
            this.prepareException((AuthenticationException)lastException, authentication);
        }

        throw lastException;
    }
}

拓展:

ProviderManager可以配置一個AuthenticationManager作為parent, 當(dāng)ProviderManager認(rèn)證失敗后, 可以進(jìn)入parent中再次進(jìn)行認(rèn)證, 通常由ProviderManager來充當(dāng)parent的角色, 即ProviderManagerProviderManager的parent
ProviderManager可以有多個, 而多個ProviderManager共用一個parent

(4) 當(dāng)前AuthenticationProvider支持認(rèn)證時, 會進(jìn)入AuthenticationProviderauthenticate方法, 而AuthenticationProvider是一個接口, 它的實現(xiàn)類是AbstractUserDetailsAuthenticationProvider

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
        return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
    });
    // 獲取當(dāng)前authentication的信息
    String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
    boolean cacheWasUsed = true;
    // 在緩存中查看username
    UserDetails user = this.userCache.getUserFromCache(username);
    if (user == null) {
        cacheWasUsed = false;

        try {
        	// 調(diào)用retrieveUser方法
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
        } catch (UsernameNotFoundException var6) {
            this.logger.debug("User '" + username + "' not found");
            if (this.hideUserNotFoundExceptions) {
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }

            throw var6;
        }

        Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
    }

    try {
        this.preAuthenticationChecks.check(user);
        this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
    } catch (AuthenticationException var7) {
        if (!cacheWasUsed) {
            throw var7;
        }

        cacheWasUsed = false;
        user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
        this.preAuthenticationChecks.check(user);
        // 密碼的加密處理
        this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
    }

    this.postAuthenticationChecks.check(user);
    if (!cacheWasUsed) {
        this.userCache.putUserInCache(user);
    }

    Object principalToReturn = user;
    if (this.forcePrincipalAsString) {
        principalToReturn = user.getUsername();
    }

    return this.createSuccessAuthentication(principalToReturn, authentication, user);
}

(5) retrieveUserAbstractUserDetailsAuthenticationProvider中有retrieveUser方法, 但是實現(xiàn)該方法的對象是DaoAuthenticationProvider, 該對象重寫了retrieveUser方法, 在retrieveUser方法中, 可以看到調(diào)用了UserDetailsServiceloadUserByUsername()方法, 該方法用來根據(jù)用戶名查詢內(nèi)存或者其他數(shù)據(jù)源中的用戶. 默認(rèn)是基于內(nèi)存查找, 我們可以自定義為數(shù)據(jù)庫查詢. 查詢后的結(jié)果封裝成UserDetails 對象, 該對象包含用戶名、加密密碼、權(quán)限以及賬戶相關(guān)信息. 密碼的加密處理是SpringSecurity幫我們處理

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    this.prepareTimingAttackProtection();

    try {
    	// 調(diào)用該方法返回一個UserDetails 對象
        UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        } else {
            return loadedUser;
        }
    } catch (UsernameNotFoundException var4) {
        this.mitigateAgainstTimingAttack(authentication);
        throw var4;
    } catch (InternalAuthenticationServiceException var5) {
        throw var5;
    } catch (Exception var6) {
        throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
    }
}

三. JWT

1. 什么是JWT?

JWT主要用于用戶登陸鑒權(quán), 在之前可能會使用session和token認(rèn)證, 下面簡述三者session和JWT的區(qū)別

Session

用戶向服務(wù)器發(fā)送一個請求時, 服務(wù)器并不知道該請求是誰發(fā)的, 所以在用戶發(fā)送登錄請求時, 服務(wù)器會將用戶提交的用戶名和密碼等信息保存在session會話中(一段內(nèi)存空間)。同時服務(wù)器保存的用戶信息會生成一個sessionid(相當(dāng)于用戶信息是一個value值, 而sessionid是value值的key)返回給客戶端, 客戶端將sessionid保存到cookie中, 等到下一次請求客戶端會將cookie一同請求給服務(wù)器做認(rèn)證

如果用戶過多, 必然會耗費(fèi)大量內(nèi)存, 在cookie中存放sessionid會存在暴露用戶信息的風(fēng)險

Token

token是一串隨機(jī)的字符串也叫令牌, 其原理和session類似, 當(dāng)用戶登錄時, 提交的用戶名和密碼等信息請求給服務(wù)端, 服務(wù)端會根據(jù)用戶名或者其他信息生成一個token而不是sessionid, 這和sessionid唯一區(qū)別就是, token不再存儲用戶信息, 客戶端下一次請求會攜帶token, 此時服務(wù)器根據(jù)此次token進(jìn)行認(rèn)證。

token認(rèn)證時也會到數(shù)據(jù)庫中查詢, 會造成數(shù)據(jù)庫壓力過大。

JWT

JWT將登錄時所有信息都存在自己身上, 并且以json格式存儲, JWT不依賴Redis或者數(shù)據(jù)庫, JWT安全性不太好, 所以不能存儲敏感信息

2. SpringSecurity集成JWT

(1) 認(rèn)證配置

a) 配置SpringSecurity

首先配置一個SpringSecurity的配置類, 因為是基于JWT進(jìn)行認(rèn)證, 所以需要在配置中禁用session機(jī)制, 并不是禁用整個系統(tǒng)的session功能

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserServiceImpl userDetailsService;
    @Autowired
    private LoginFilter loginFilter;
    @Autowired
    private AuthFilter authFilter;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 禁用session機(jī)制 
        http.csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.authorizeRequests()
                // 指定某些接口不需要通過驗證即可訪問。像登陸、注冊接口肯定是不需要認(rèn)證的
                .antMatchers("/sec/login").permitAll()
                .anyRequest().authenticated()

                // 自定義權(quán)限配置
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(customUrlDecisionManager);
                        o.setSecurityMetadataSource(customFilter);
                        return o;
                    }
                })
                .and()
                // 禁用緩存
                .headers()
                .cacheControl();

        http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        // 添加自定義未授權(quán)和未登陸結(jié)果返回
        http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthoricationEntryPoint);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定UserDetailService和加密器
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean     
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

b) 實現(xiàn)登錄接口

先按照正常流程, 實現(xiàn)一個登錄的接口然后在業(yè)務(wù)層中實現(xiàn)

@PostMapping("/login")
public Res login(@RequestBody User user, HttpServletRequest request) {
    return userService.login(user, request);
}

在業(yè)務(wù)層中, 首先對密碼和用戶名進(jìn)行檢驗, 然后更新security登錄用戶對象, 在此之前我們先來認(rèn)識幾個在SpringSecurity中重要的變量

  • Authentication: 存儲了認(rèn)證信息, 代表登錄用戶
  • SecurityContext: 上下文對象, 用來獲取Authentication(用戶信息)
  • SecurityContextHolder: 上下文管理對象, 用來在程序任何地方獲取SecurityContext
  • UserDetails: 存儲了用戶的基本信息, 以及用戶權(quán)限、是否被禁用等

Authentication中的認(rèn)證信息有

  • Principal: 用戶信息
  • Credentials: 用戶憑證, 一般是密碼
  • Authorities: 用戶權(quán)限
@Override
public Res login(User user, HttpServletRequest request) {
    String username = user.getUsername();
    String password = user.getPassword();

    // 登陸 檢測
    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
    if(null == userDetails || !passwordEncoder.matches(password, userDetails.getPassword())) {
        return Res.error("用戶名或密碼不正確!");
    }
    // 更新security登錄用戶對象
    UsernamePasswordAuthenticationToken authenticationToken = new
            UsernamePasswordAuthenticationToken(userDetails,
            null, userDetails.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(authenticationToken);

    // 創(chuàng)建一個token
    String token = jwtTokenUtil.generateToken(userDetails);
    Map<String, String> tokenMap = new HashMap<>();
    tokenMap.put("token", token);
    tokenMap.put("tokenHead", tokenHead);
    return Res.success("登陸成功", tokenMap);
}

下面這行代碼主要是在數(shù)據(jù)庫或者緩存中查詢用戶提交的用戶名以及用戶的權(quán)限信息, 將這些信息保存在userDetails

UserDetails userDetails = userDetailsService.loadUserByUsername(username);

UsernamePasswordAuthenticationToken 實現(xiàn)了Authentication, 也就是說此時將userDetails中的信息以及權(quán)限信息存放在Authentication

創(chuàng)建Token需要JWT的工具類, 在網(wǎng)上隨便找個都可以, 大致都一樣, 這個只需要知道就行了

c) 過濾請求

在原生SpringSecurity中默認(rèn)的攔截在UsernamePasswordAuthenticationFilter這個類中,該類主要攔截表單提交的用戶名和密碼, 顯然在前后端分離項目中不適用, 而且我們用到了JWT的驗證方式, 前端每次請求都需要帶上token, 所以我們需要在后端對每個請求進(jìn)行提前過濾攔截

public class JwtAuthencationTokenFilter extends OncePerRequestFilter {
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 請求頭中獲取token信息
        String authheader = request.getHeader(tokenHeader);
        // 存在token
        if(null != authheader && authheader.startsWith(tokenHead)) {
            // 去除字段名稱, 獲取真正token
            String authToken = authheader.substring(tokenHead.length());
            // 利用token獲取用戶名
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            // token存在用戶但未登陸
            // SecurityContextHolder.getContext().getAuthentication() 獲取上下文對象中認(rèn)證信息
            if(null != username && null == SecurityContextHolder.getContext().getAuthentication()) {
                // 自定義數(shù)據(jù)源獲取用戶信息
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                // 驗證token是否有效 驗證token用戶名和存儲的用戶名是否一致以及是否在有效期內(nèi), 重新設(shè)置用戶對象
                if(jwtTokenUtil.validateToken(authToken, userDetails)) {
                    // 重新將用戶信息封裝到UsernamePasswordAuthenticationToken
                    UsernamePasswordAuthenticationToken authenticationToken = new
                            UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    // 將信息存入上下文對象
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }
        filterChain.doFilter(request,response);
    }
}

該過濾器主要做的是:

  1. 提取前端發(fā)送的請求頭信息, 根據(jù)JWT的工具類獲取用戶名
  2. 如果請求頭具有有效的字符串(也就是擁有用戶信息)并且上下文對象存在用戶信息(數(shù)據(jù)庫或者緩存中查的用戶信息)則直接到下一個過濾器, 否則請求頭中有信息而當(dāng)前上下文對象沒有存儲用戶信息則將請求頭中的用戶在數(shù)據(jù)層驗證之后重新放入上下文對象中(UsernamePasswordAuthenticationToken)。
  3. 如果當(dāng)前用戶沒有登錄或者沒有token信息(可能是token過期), 而當(dāng)前請求的地址符合權(quán)限中包含的地址(也就是數(shù)據(jù)庫中存在的), 則會進(jìn)入權(quán)限驗證(下面會講)

當(dāng)然以上的邏輯可以自己自定義, 不管以上什么情況都會進(jìn)入權(quán)限驗證

要讓這個過濾器加入到SpringSecurity的過濾器鏈中, 就需要在SecurityConfig類的configure方法添加下面一條語句, addFilterBefore()jwtAuthencationTokenFilter(), 放在UsernamePasswordAuthenticationFilter之前

http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

(2) 權(quán)限配置

在一個項目中, 不同的用戶需要具有不同的權(quán)限, 我們怎么對用戶進(jìn)行區(qū)分呢?

a) RBAC權(quán)限表

將用戶、角色和權(quán)限綁定,這樣可以知道某個用戶具有哪些角色, 而某個角色對應(yīng)有哪些權(quán)限(能干什么,不能干什么),這樣就知道哪些用戶擁有的角色和權(quán)限信息。

基于以上的想法, 我們需要三張實體表, 還需要兩張多對多的關(guān)系表, 這樣就構(gòu)成了RBAC的五張表

b) 授權(quán)流程

在SpringSecurity中授權(quán)的過濾器是FilterSecurityInterceptor

默認(rèn)的流程

  • 調(diào)用SecurityMetadataSource獲取當(dāng)前請求的鑒權(quán)規(guī)則
  • 接著調(diào)用AccessDecisionManager 來校驗當(dāng)前用戶的是否擁有當(dāng)前權(quán)限
  • 如果有權(quán)限就放行, 否則拋出異常, 該異常則會被AccessDeniedHandler處理

c) 自定義SecurityMetadataSource

@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        if(Collections.isEmpty(collection)) {
            return;
        }
        for (ConfigAttribute configAttribute : collection) {
            for (GrantedAuthority authority : authentication.getAuthorities()) {
            	if("ROLE_ANONYMOUS".equals(authority.getAuthority())) {
                    throw new AccessDeniedException("尚未登錄, 請登錄");
                }
                if(Objects.equals(authority.getAuthority(), configAttribute.getAttribute())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("權(quán)限不足, 請聯(lián)系管理員!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

FilterInvocationSecurityMetadataSource繼承SecurityMetadataSource

getAttributes方法中, o參數(shù)封裝了request的相關(guān)信息, 可以從中獲取請求的方法和URL等信息

然后menus得到的是當(dāng)前數(shù)據(jù)層中所有的權(quán)限路徑, 接著循環(huán)所有的路徑信息與當(dāng)前請求的方法和URL進(jìn)行驗證, 如果在數(shù)據(jù)層中沒有當(dāng)前請求則返回null, 否則將該權(quán)限的在數(shù)據(jù)層中的信息返回

c) 自定義AccessDecisionManager

如果在SecurityMetadataSource中有權(quán)限信息, 則會進(jìn)入該方法

@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        if(Collections.isEmpty(collection)) {
            return;
        }
        for (ConfigAttribute configAttribute : collection) {
            for (GrantedAuthority authority : authentication.getAuthorities()) {
            	if("ROLE_ANONYMOUS".equals(authority.getAuthority())) {
                    throw new AccessDeniedException("尚未登錄, 請登錄");
                }
                if(Objects.equals(authority.getAuthority(), configAttribute.getAttribute())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("權(quán)限不足, 請聯(lián)系管理員!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

首先來看幾個變量

  • ConfigAttribute: 這個是鑒權(quán)的規(guī)則, 根據(jù)自己項目設(shè)定, 我們這里填入的是當(dāng)前請求和數(shù)據(jù)層中相匹配的權(quán)限信息id
  • GrantedAuthority: 當(dāng)前認(rèn)證用戶所擁有的權(quán)限信息

在上述的decide方法中, 主要驗證了用戶所擁有的權(quán)限和當(dāng)前請求的權(quán)限信息是否一致

如果不一致, 則拋出異常, 被AccessDeniedHandler處理

d) 自定義AccessDeniedHandler

自定義返回json格式數(shù)據(jù)

@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        Res bean = Res.error("權(quán)限不足, 請聯(lián)系管理員!");
        bean.setCode(403);
        out.write(new ObjectMapper().writeValueAsString(bean));
        out.flush();
        out.close();
    }
}

e) 在SpringSecurity中的配置

在configure方法中, 進(jìn)行了動態(tài)權(quán)限配置

.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
    @Override
    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
        o.setAccessDecisionManager(customUrlDecisionManager);
        o.setSecurityMetadataSource(customFilter);
        return o;
    }
})

插入: 還有一個認(rèn)證異常處理

  1. 用戶首次登錄且驗證成功, 此時正常用戶權(quán)限授權(quán)
  2. 請求數(shù)據(jù)時, 非首次登錄, 如果沒有攜帶token(token過期), 又或者沒有登錄訪問內(nèi)部路徑時, 說明沒有認(rèn)證權(quán)限不能訪問, 拋出未登錄異常
  3. 請求數(shù)據(jù)時, 有token信息, 而上下文對象中沒有用戶信息, 則會重新將用戶信息放入上下文對象中, 接著進(jìn)入權(quán)限驗證, 如果用戶擁有該權(quán)限則放行, 如果沒有該權(quán)限則拋出權(quán)限不足異常

在configure中配置未登錄和未授權(quán)異常處理

http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthoricationEntryPoint);

四. 總結(jié)

其實以上配置還有很多漏洞, 比如token的過期時間, 當(dāng)用戶上一秒還在請求數(shù)據(jù), 下一秒token過期, 則會造成用戶需要重新登錄, 顯然不合適

這是項目的地址 Github下載

到此這篇關(guān)于Spring Security+JWT簡述的文章就介紹到這了,更多相關(guān)Spring Security JWT簡述內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • MyBatis工廠類封裝與簡化實現(xiàn)

    MyBatis工廠類封裝與簡化實現(xiàn)

    工廠類的目的是將對象的創(chuàng)建邏輯封裝在一個類中,以便客戶端代碼無需了解具體的實現(xiàn)細(xì)節(jié),本文主要介紹了MyBatis工廠類封裝與簡化實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • idea如何自動生成serialVersionUID

    idea如何自動生成serialVersionUID

    這篇文章主要介紹了idea如何自動生成serialVersionUID,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02
  • java中的接口能夠被實例化嗎

    java中的接口能夠被實例化嗎

    這篇文章主要介紹了java中的接口能夠被實例化嗎,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Java實現(xiàn)俄羅斯方塊游戲的示例代碼

    Java實現(xiàn)俄羅斯方塊游戲的示例代碼

    俄羅斯方塊是一個最初由阿列克謝帕吉特諾夫在蘇聯(lián)設(shè)計和編程的益智類視頻游戲。本文將利用Java實現(xiàn)這一經(jīng)典的小游戲,感興趣的可以動手試一試
    2022-03-03
  • Java弱引用集合WeakHashMap總結(jié)

    Java弱引用集合WeakHashMap總結(jié)

    這篇文章主要介紹了Java弱引用集合WeakHashMap總結(jié),WeakHashMap利用WeakReference的弱引用特性讓用戶在使用的過程中不會因為沒有釋放Map中的資源而導(dǎo)致內(nèi)存泄露,WeakHashMap實現(xiàn)了Map接口,使用方式和其他的Map相同,需要的朋友可以參考下
    2023-09-09
  • mybatis?plus更新字段為null處理方法

    mybatis?plus更新字段為null處理方法

    這篇文章主要為大家介紹了將mybatis?plus更新字段為null的處理方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2022-02-02
  • jackson設(shè)置返回null為空字符串的操作

    jackson設(shè)置返回null為空字符串的操作

    這篇文章主要介紹了jackson設(shè)置返回null為空字符串的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • SpringSecurity 手機(jī)號登錄功能實現(xiàn)

    SpringSecurity 手機(jī)號登錄功能實現(xiàn)

    這篇文章主要介紹了SpringSecurity 手機(jī)號登錄功能實現(xiàn),本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2023-12-12
  • springboot攔截器Interceptor的使用,你都了解嗎

    springboot攔截器Interceptor的使用,你都了解嗎

    springmvc 中的攔截器可以對請求進(jìn)行判別,在請求到達(dá)控制器之前,把非法的請求給攔截掉下面來說一說, 它在springboot中的使用,感興趣的朋友一起看看吧
    2021-07-07
  • 關(guān)于Spring中的@Configuration中的proxyBeanMethods屬性

    關(guān)于Spring中的@Configuration中的proxyBeanMethods屬性

    這篇文章主要介紹了關(guān)于Spring中的@Configuration中的proxyBeanMethods屬性,需要的朋友可以參考下
    2023-07-07

最新評論