https://blog.csdn.net/Lammonpeter/article/details/79611439php
https://www.bilibili.com/video/av40943281java
http://www.javashuo.com/article/p-rgyffmcv-nn.htmlgit
l流程 :SecurityContext裝配<——>認證登陸< ——>異常<——>鑑權<——>Mvc(dispatchServlet)redis
圖中爲過濾器鏈流程中的一些核心過濾器,請求線程chain.doFilter()方法向下調用過濾器。整個過程是同一個線程的方法棧,後進先出。圖中請求線是進棧,響應線是出棧。spring
第一個橙色的過濾器是請求進入時根據SessionID檢查Session(本地/分佈式redis等)中是否已存在SecurityContext,若存在則放入SecurityContextHolder中做爲線程變量。響應返回退出時,他是最後一道經過,會清除SecurityContextHolder,將SecurityContext放到Session中。保證不一樣請求線程能根據SessionID從Session中取得對應用戶的SecurityContext。緩存
其中綠色爲認證過濾器。第一個綠色爲不一樣認證功能對應的不一樣過濾器(根據不一樣的登陸方式選擇不一樣的過濾器),最後一個綠色是全部請求都會通過的匿名過濾器。session
匿名過濾器最後檢查SecurityContextHolder.getContext().getAuthentication()==null,如果真則當前線程在前面的認證過濾器沒有從持久層或是sesson中獲得用戶信息,匿名過濾器會統一爲當前線程添加一個匿名Authentication到SecurityContextHolder。數據結構
最後一個FilterSecurityInterceptor,是全部請求都會通過的最後一個鑑權過濾器,他是鑑權的核心實現。經過它就會訪問到controller,不經過會拋出異常給藍色的異常過濾器處理。app
由於鏈上的都是過濾器,因此Security在dispatchServlet以前執行。既在攔截器+AOP以前。分佈式
經過SecurityContextHolder取得當前線程對應用戶的信息。
方法一:SecurityContextHolder
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User principal = (org.springframework.security.core.userdetails.User) authentication.getPrincipal(); return principal.getUsername();
方法二:Spring自動注入
@RequestMapping("/url") public String echo2(Authentication authentication) { } @RequestMapping("/url2") public String echo(@AuthenticationPrincipal UserDetails user) { }
用戶是經過用戶名密碼的方式登錄的,因此Authentication是UsernamePasswordAuthenticationToken類型
驗證登陸信息,建立用戶Authentication,放入SecurityContextHolder,最終將SecurityContext存入Session中。
以後請求直接從Session中取出SecurityContext。
用戶權限信息+URL權限信息+決策器實現 鑑權工做
FilterSecurityInterceptor中核心驗證方法
public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) { //經過Request中的屬性,判斷是否已經通過此過濾器,是則放行 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { //首次進入 在Request添加屬性 if (fi.getRequest() != null && observeOncePerRequest) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } //beforeInvocation鑑權 若鑑權失敗 拋異常 InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); } }
beforeInvocation方法是鑑權的核心(URL權限緩存+決策器+用戶信息)
其經過securityMetadataSource.getAttributes()讀取url對應的權限,將(用戶信息+ request+url權限)傳入 accessDecisionManager.decide(authenticated, object, attributes)方法進行決策。
protected InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); final boolean debug = logger.isDebugEnabled(); if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { throw new IllegalArgumentException( "Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + getSecureObjectClass()); } //SecurityMetadataSource取得URL對應權限 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource() .getAttributes(object); if (attributes == null || attributes.isEmpty()) { if (rejectPublicInvocations) { throw new IllegalArgumentException( "Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. " + "This indicates a configuration error because the " + "rejectPublicInvocations property is set to 'true'"); } if (debug) { logger.debug("Public object - authentication not attempted"); } publishEvent(new PublicInvocationEvent(object)); return null; // no further work post-invocation } if (debug) { logger.debug("Secure object: " + object + "; Attributes: " + attributes); } if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(messages.getMessage( "AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); } Authentication authenticated = authenticateIfRequired(); // Attempt authorization try { //accessDecisionManager決策器經過(用戶信息+ request+url權限)鑑權 this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } if (debug) { logger.debug("Authorization successful"); } if (publishAuthorizationSuccess) { publishEvent(new AuthorizedEvent(object, attributes, authenticated)); } // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs == null) { if (debug) { logger.debug("RunAsManager did not change Authentication object"); } // no further work post-invocation return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); } else { if (debug) { logger.debug("Switching to RunAs Authentication: " + runAs); } SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); } }
自定義FilterSecurityInterceptor(鑑權過濾器):繼承AbstractSecurityInterceptor,使用自定義的securityMetadataSource+accessDecisionManager。調用super.beforeInvocation進行鑑權。
自定義securityMetadataSource(URL權限緩存):實現FilterInvocationSecurityMetadataSource接口,
自定義數據結構保存URL權限SecurityConfig,覆蓋實現getAttributes()讀取url對應的權限,爲決策器提供URL權限。
自定義accessDecisionManager(決策器):實現AccessDecisionManager接口,實現decide(authenticated, object, attributes)方法經過(用戶信息+ request+url權限)進行匹配決策。
https://blog.csdn.net/shanchahua123456/article/details/88949064
簡單用例
每一個用戶都有本身的Authentication,其保存在SecurityContextHolder中。Authentication是經過SpringSecurity的UserDetial實現填充信息。
@GetMapping("/vip/test") @Secured("ROLE_VIP") // 須要ROLE_VIP權限可訪問 public String vipPath() { return "僅 ROLE_VIP 可看"; } @GetMapping("/vip") public boolean updateToVIP() { // 獲得當前的認證信息 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // 生成當前的全部受權 List<GrantedAuthority> updatedAuthorities = new ArrayList<>(auth.getAuthorities()); // 添加 ROLE_VIP 受權 updatedAuthorities.add(new SimpleGrantedAuthority("ROLE_VIP")); // 生成新的認證信息 Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), updatedAuthorities); // 重置認證信息 SecurityContextHolder.getContext().setAuthentication(newAuth); return true; }
假設當前你的權限只有 ROLE_USER。那麼按照上面的代碼:
一、直接訪問 /vip/test 路徑將會獲得403的Response;
二、訪問 /vip 獲取 ROLE_VIP 受權,再訪問 /vip/test 便可獲得正確的Response。
轉自http://www.spring4all.com/article/155
OncePerRequestFilter: https://blog.csdn.net/f641385712/article/details/87793736
自定義Security過濾器
HttpSessionRequestCache
受權表達式放在antMatchers(URL)以後
1 對GET請求,URL="/user/{id}"權限攔截
authorizeRequests().antMatchers(HttpMethod.GET,"/user/*").hasRole("ADMIN")
2 經過hasRole方法底層源碼能夠看到最終拼接的權限表達式是"hasRole('ROLE_ADMIN')"。因此用戶權限對應的是ROLE_ADMIN。
private static String hasRole(String role) { Assert.notNull(role, "role cannot be null"); if (role.startsWith("ROLE_")) { throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'"); } else { return "hasRole('ROLE_" + role + "')"; } }
3 hasAuthority()與hasRole()不一樣,其實徹底匹配,hasRole是自動加ROLE。
好比:
hasAuthority("read") 用戶須要"read"權限
hasRole("read") 用戶須要"ROLE_read"權限
4 符合配置
authorizeRequests().antMatchers("/user/*").access("hasRole('ADMIN) and hasAuthority('read') ")
https://www.e-learn.cn/index.php/content/redis/730910
在springsecurity配置中,註冊spring session redis 的sessionregistry。