在上一篇《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电商系统源码中关于权限相关的分享。
来源:oschina
链接:https://my.oschina.net/u/4385177/blog/3226594