在上一篇《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电商系统源码中关于权限相关的分享。