在上一篇《b2b2c系統jwt許可權源碼分享part1》中和大家分享了b2b2c系統中jwt許可權的基礎設計及源碼,本文繼續和大家分享jwt和spring security整合部分的思路和源碼。 在上一篇文章中已經分享了關鍵的類圖: 如上圖所示,許可權的校驗主要涉及到四個類: AbstractAuthen ...
在上一篇《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電商系統源碼中關於許可權相關的分享。