b2b2c系統jwt權限源碼分享part2

在上一篇《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電商系統源碼中關於權限相關的分享。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章