1. 定义一个使用 账号和密码登录 Realm

public class CustomRealm extends AuthorizingRealm {

    @Autowired
    RedisUtil redisUtil;

    @Override
    public String getName(){
        return "myRealm";
    }
    @Autowired
    private IUserService userService;

    /**
     * 进行权限校验的时候会调用
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
       log.info("授权 doGetAuthorizationInfo");
        String token = (String) principalCollection.getPrimaryPrincipal();
        User currentUser = redisOperator.getAccUserInfo(token);
        User user = userService.findAllUserInfoByUsername(currentUser.getId());

        Set<String> stringRoleList = new HashSet<>();
        List<String> stringPermissionList = new ArrayList<>();
        List<AccRole> roleList = user.getRoleList();
        for (AccRole role : roleList) {
            stringRoleList.add(role.getRoleName());
            List<UserPermission> permissionList = role.getPermissionsList();
            for (UserPermission permission : permissionList) {
                if (permission != null) {
                    stringPermissionList.add(permission.getPermissionName());
                }
            }
        }

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(stringRoleList);
        simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
        return simpleAuthorizationInfo;
    }

    /**
     * 用户认证登录的时候会调用
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("认证 doGetAuthenticationInfo");
        // 从token获取用户信息,token代表用户输入
        String username = (String) authenticationToken.getPrincipal();
        AccUser currentUser = accUserService.getUserByUsername(username);
        AccUser user = accUserService.findAllUserInfoByUsername(currentUser.getId());
        // 取密码
        String pwd = user.getPassword();
        if (StringUtils.isEmpty(pwd)) {
            return null;
        }
        return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
    }
}

2. 定义一个 token 结构体

public class CustomToken implements HostAuthenticationToken {

    private String appid;

    private String appKey;

    private String token;

    private String uid;

    public CustomToken(String appid, String appKey, String token,String uid) {
        this.appid = appid;
        this.appKey = appKey;
        this.token = token;
        this.uid = uid;
    }

    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public String getAppKey() {
        return appKey;
    }

    public void setAppKey(String appKey) {
        this.appKey = appKey;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    @Override
    public String getHost() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

3. 定义一个自定义 Shiro 异常

public class CustomAuthenticationException extends AuthenticationException {
    private Integer code;


    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public CustomAuthenticationException( Integer code,String message) {
        super(message);
        this.code = code;
    }

    public CustomAuthenticationException(ReturnEnum returnEnum) {
        super(returnEnum.getRet_msg());
        this.code = returnEnum.getRet_code();
    }
}

4. 定义一个用于 token 验证的 Realm

public class TokenRealm extends AuthorizingRealm {

    private static final Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof CustomToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.debug("进行token 认证 ............");
        CustomToken customToken = (CustomToken) authenticationToken;

        RedisUtil redisUtil = SpringContextBean.getBean(redisUtil.class);
        IUserService userService = SpringContextBean.getBean(IUserService.class);

        String uid = customToken.getUid();
        String appid = customToken.getAppid();
        String token = customToken.getToken();
        if (StringUtils.isEmpty(appid)) {
            //
            if (!redisTempleUtil.hHasKey(GlobalConstant.USER_KEY, customToken.getToken())) {
                throw new CustomAuthenticationException(ReturnEnum.UNKNOWN_TOKEN);
            }
        } else {
            if (StringUtils.isEmpty(uid)) {
                throw new CustomAuthenticationException(ReturnEnum.PARAM_INVALID.getRet_code(), "uId " + ReturnEnum.PARAM_INVALID.getRet_msg());
            }
            ReturnResult<JSONObject> authRes = userService.authToken(uid, appid, token);
            log.warn("请求结果: {}",authRes);
            if (authRes.getRet_code() != ReturnEnum.OK.getRet_code()) {

                throw new CustomAuthenticationException(authRes.getRet_code(), authRes.getRet_msg());
            }

        }

        return new SimpleAuthenticationInfo(token,token,"tokenRealm");
    }
}

5. 创建一个用于 Filter 处理 token

public class TokenFilter extends AuthenticatingFilter {

    private static final Logger log = LoggerFactory.getLogger(AuthenticatingFilter.class);

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (this.isLoginRequest(request,response)) { return true;}
        boolean allowed = false;
        try {
            allowed = executeLogin(request,response);
        } catch (IllegalAccessException e) {
            log.error("未查找到token");
            throw  new RuntimeException(e.getMessage());
        }catch (Exception e) {
            log.error("登录过程发生异常",e);
            throw  new RuntimeException(e.getMessage());
        }
        return allowed || super.isPermissive(mappedValue);
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        CustomToken token = (CustomToken) this.createToken(request,response);
        if (token == null) {
            return false;
        }else {
            try {
                Subject subject = this.getSubject(request, response);
                subject.login(token);
                return this.onLoginSuccess(token, subject, request, response);
            } catch (AuthenticationException var5) {
                return this.onLoginFailure(token, var5, request, response);
            }
        }
    }

    /**
     * 这里重写了父类的方法,使用我们自己定义的Token类,提交给shiro。
     * 这个方法返回null的话会直接抛出异常,进入isAccessAllowed()的异常处理逻辑。
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        String token = getHeader((HttpServletRequest)servletRequest, GlobalConstant.TOKEN_NAME);
        String uid = getHeader((HttpServletRequest)servletRequest, GlobalConstant.UID);
        String appid = getHeader((HttpServletRequest)servletRequest, GlobalConstant.APPID);
        String appkey = getHeader((HttpServletRequest)servletRequest, GlobalConstant.APP_KEY);
        if (!StringUtils.isEmpty(token)) {
            // 如果 token 不为空
            return new CustomToken(appid,appkey,token,uid);
        }
        return null;
    }


    private String getHeader(HttpServletRequest request,String headerName) {
        return request.getHeader(headerName);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletResponse httpResponse = WebUtils.toHttp(servletResponse);
        httpResponse.setCharacterEncoding("UTF-8");
        httpResponse.setContentType("application/json;charset=UTF-8");
        httpResponse.setStatus(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
        httpResponse.getWriter().write(JSON.toJSONString(ResponseVO.error(ReturnEnum.TOKEN_MISS.getRet_code(), ReturnEnum.TOKEN_MISS.getRet_msg())));
        return false;
    }
}

6. 角色过滤 filter

public class CustomRolesOrAuthorizationFilter  extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
        Subject subject = getSubject(servletRequest, servletResponse);

        //获取当前访问路径所需要的角色集合
        String[] rolesArray = (String[]) mappedValue;

        //没有角色限制,有权限访问
        if (rolesArray == null || rolesArray.length == 0) {
            return true;
        }

        //当前subject是rolesArray中的任何一个,则有权限访问
        for (int i = 0; i < rolesArray.length; i++) {
            if (subject.hasRole(rolesArray[i])) {
                return true;
            }
        }
        return false;
    }
}

7. 创建 shiro 的 config

public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //必须设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //需要登录的接口,如果访问该接口却没有登录,则调用此接口
        shiroFilterFactoryBean.setLoginUrl("/user/need_login");

        //登录成功跳转URL(可以忽略)
        shiroFilterFactoryBean.setSuccessUrl("/");

        //已登录但没有权限访问要访问的接口,则调用此接口   (先验证登录->在验证权限)
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/not_permit");

        //拦截器路径必须是LinkedHashMap,否则可能导致部分路径不起作用,因为hashmap是无序的
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //设置自定义filter
        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        filtersMap.put("authcToken",new TokenFilter());
        filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");
        shiroFilterFactoryBean.setFilters(filtersMap);

        //匿名可以访问的(游客)
        filterChainDefinitionMap.put("/doc.html/**","anon");
        filterChainDefinitionMap.put("/user/login","anon");
        filterChainDefinitionMap.put("/error","anon");
        filterChainDefinitionMap.put("/swagger-resources/**","anon");
        filterChainDefinitionMap.put("/webjars/**","anon");
        filterChainDefinitionMap.put("/v2/**","anon");
        filterChainDefinitionMap.put("/swagger-ui.html#/","anon");
        //超级管理员可以访问的
//        filterChainDefinitionMap.put("/root/**","roles[root]");

        //过滤连是从上往下执行,将/**放到最后
        //authc:url必须经过定义才能访问
        //anon:url可以匿名访问
        filterChainDefinitionMap.put("/**", "noSessionCreation,authcToken");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //设置realm推荐放到最后
        securityManager.setRealms(Arrays.asList(customRealm(),tokenRealm()));
        return securityManager;
    }

    /**
     * 自定义realm
     * @return
     */
    @Bean
    public CustomRealm customRealm(){
        CustomRealm customRealm = new CustomRealm();
        // 密码加解密规则
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //设置散列算法
        credentialsMatcher.setHashAlgorithmName("md5");
        //散列次数,相当于多次md5
        credentialsMatcher.setHashIterations(2);

        customRealm.setCredentialsMatcher(credentialsMatcher);
        return customRealm;
    }

    @Bean
    public TokenRealm tokenRealm(){
        TokenRealm tokenRealm = new TokenRealm();
        return tokenRealm;
    }

    /**
     * 初始化Authenticator
     */
    @Bean
    public Authenticator authenticator( ) {
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        //设置两个Realm,一个用于用户登录验证和访问权限获取;一个用于jwt token的认证
        authenticator.setRealms(Arrays.asList(customRealm(),tokenRealm()));
        //设置多个realm认证策略,一个成功即跳过其它的
        authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
        return authenticator;
    }

    /**
     * 管理shiro一些bean的生命周期 即bean初始化 与销毁
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,
     * 需要在LifecycleBeanPostProcessor创建后才可以创建
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

}