在上一篇《b2b2c系統jwt權限原始碼分享part1》中和大家分享了b2b2c系統中jwt權限的基礎設計及原始碼,本文繼續和大家分享jwt和spring security整合部分的思路和原始碼,
在上一篇文章中已經分享了關鍵的類圖:
如上圖所示,權限的校驗主要涉及到四個類:
-
AbstractAuthenticationService
-
BuyerAuthenticationService
-
SellerAuthenticationService
-
AdminAuthenticationService
AbstractAuthenticationService
對于三端(買家買家管理端)驗權的公用部分我們抽象在AbstractAuthenticationService中:
public abstract class AbstractAuthenticationService implements AuthenticationService { @Autowired protected TokenManager tokenManager; private final Logger logger = LoggerFactory.getLogger(getClass()); /** * 單例模式的cache */ private static Cache<String, Integer> cache; @Autowired private JavashopConfig javashopConfig; /** * 鑒權,先獲取token,再根據token來鑒權 * 生產環境要由nonce和時間戳,簽名來獲取token * 開發環境可以直接傳token * * @param req */ @Override public void auth(HttpServletRequest req) { String token = this.getToken(req); if (StringUtil.notEmpty(token)) { Authentication authentication = getAuthentication(token); if (authentication != null) { SecurityContextHolder.getContext().setAuthentication(authentication); } } } /** * 接收用戶禁用或解禁事件<br/> * 禁用:將被禁用的用戶id寫入快取 * 解禁:將快取中存放的用戶id洗掉 * * @param userDisableMsg */ @Override public void userDisableEvent(UserDisableMsg userDisableMsg) { //在快取中記錄用戶被禁用 Cache<String, Integer> cache = this.getCache(); if (UserDisableMsg.ADD.equals(userDisableMsg.getOperation())) { logger.debug("收到用戶禁用訊息:" + userDisableMsg); cache.put(getKey(userDisableMsg.getRole(), userDisableMsg.getUid()), 1); } if (UserDisableMsg.DELETE.equals(userDisableMsg.getOperation())) { logger.debug("收到用戶解禁訊息:" + userDisableMsg); cache.remove(getKey(userDisableMsg.getRole(), userDisableMsg.getUid()), 1); } } protected void checkUserDisable(Role role, int uid) { Cache<String, Integer> cache = this.getCache(); Integer isDisable = cache.get(getKey(role, uid)); if (isDisable == null) { return; } if (1 == isDisable) { throw new RuntimeException("用戶已經被禁用"); } } private String getKey(Role role, int uid) { return role.name() + "_" + uid; } /** * 決議一個token * 子類需要將token決議自己的子業務權限模型:Admin,seller buyer... * * @param token * @return */ protected abstract AuthUser parseToken(String token); /** * 根據一個 token 生成授權 * * @param token * @return 授權 */ protected Authentication getAuthentication(String token) { try { AuthUser user = parseToken(token); List<GrantedAuthority> auths = new ArrayList<>(); List<String> roles = user.getRoles(); for (String role : roles) { auths.add(new SimpleGrantedAuthority("ROLE_" + role)); } UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("user", null, auths); authentication.setDetails(user); return authentication; } catch (Exception e) { logger.error("認證例外", e); return new UsernamePasswordAuthenticationToken("anonymous", null); } } /** * 獲取token * 7.2.0起,廢棄掉重放攻擊的判斷 * * @param req * @return */ protected String getToken(HttpServletRequest req) { String token = req.getHeader(TokenConstant.HEADER_STRING); if (StringUtil.notEmpty(token)) { token = token.replaceAll(TokenConstant.TOKEN_PREFIX, "").trim(); } return token; } private static final Object lock = new Object(); /** * 獲取本地快取<br/> * 用于記錄被禁用的用戶<br/> * 此快取的key為:角色+用戶id,如: admin_1 * value為:1則代表此用戶被禁用 * * @return */ protected Cache<String, Integer> getCache() { if (cache != null) { return cache; } synchronized (lock) { if (cache != null) { return cache; } //快取時間為session有效期+一分鐘 //也就表示,用戶如果被禁用,session超時這個cache也就不需要了: //因為他需要重新登錄就可以被檢測出無效 int sessionTimeout = javashopConfig.getRefreshTokenTimeout() - javashopConfig.getAccessTokenTimeout() + 60; //使用ehcache作為快取 CachingProvider provider = Caching.getCachingProvider("org.ehcache.jsr107.EhcacheCachingProvider"); CacheManager cacheManager = provider.getCacheManager(); MutableConfiguration<String, Integer> configuration = new MutableConfiguration<String, Integer>() .setTypes(String.class, Integer.class) .setStoreByValue(false) .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, sessionTimeout))); cache = cacheManager.createCache("userDisable", configuration); return cache; } } }
在 javashop b2b2c系統中 禁用用戶要求該用戶立刻無法操作,這部分功能體現在
checkUserDisable方法中,思路是通過監聽redis訊息將禁用用戶放在本地cache中(這里采用的事EHCache,
BuyerAuthenticationService
有了之前的代碼基礎,三端的權限校驗就比較簡單了:
@Component public class BuyerAuthenticationService extends AbstractAuthenticationService { @Override protected AuthUser parseToken(String token) { AuthUser authUser= tokenManager.parse(Buyer.class, token); User user = (User) authUser; checkUserDisable(Role.BUYER, user.getUid()); return authUser; } }
SellerAuthenticationService
@Component public class SellerAuthenticationService extends AbstractAuthenticationService { /** * 將token決議為Clerk * * @param token * @return */ @Override protected AuthUser parseToken(String token) { AuthUser authUser = tokenManager.parse(Clerk.class, token); User user = (User) authUser; checkUserDisable(Role.CLERK, user.getUid()); return authUser; } }
AdminAuthenticationService
@Component public class AdminAuthenticationService extends AbstractAuthenticationService { /** * 將token決議為Admin * @param token * @return */ @Override protected AuthUser parseToken(String token) { AuthUser authUser= tokenManager.parse(Admin.class, token); User user = (User) authUser; checkUserDisable(Role.ADMIN, user.getUid()); return authUser; } }
整合Security:
@Configuration @EnableWebSecurity public class BuyerSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DomainHelper domainHelper; @Autowired private BuyerAuthenticationService buyerAuthenticationService; @Autowired private AccessDeniedHandler accessDeniedHandler; @Autowired private AuthenticationEntryPoint authenticationEntryPoint; /** * 定義seller工程的權限 * * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { http.cors().configurationSource((CorsConfigurationSource) ApplicationContextHolder.getBean("corsConfigurationSource")).and().csrf().disable() //禁用session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //定義驗權失敗回傳格式 .exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint).and() .authorizeRequests() .and() .addFilterBefore(new TokenAuthenticationFilter(buyerAuthenticationService), UsernamePasswordAuthenticationFilter.class); //過濾掉swagger的路徑 http.authorizeRequests().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**").anonymous(); //過濾掉不需要買家權限的api http.authorizeRequests().antMatchers("/debugger/**" ).permitAll().and(); //定義有買家權限才可以訪問 http.authorizeRequests().anyRequest().hasRole(Role.BUYER.name()); http.headers().addHeaderWriter(xFrameOptionsHeaderWriter()); //禁用快取 http.headers().cacheControl().and() .contentSecurityPolicy("script-src 'self' 'unsafe-inline' ; frame-ancestors " + domainHelper.getBuyerDomain()); }
以上就是javashop電商系統原始碼中關于權限相關的分享,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/5998.html
標籤:架構設計
上一篇:TCP粘包處理現象及其解決方案——基于NewLife.Net網路庫的管道式幀長粘包處理方法
下一篇:高德深度資訊接入的平臺化演進
