Java整合Shiro使用详情

一、导入依赖

<!--shiro依赖-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.8.0</version>
</dependency>

<!--shiro-redis依赖-->
<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>2.4.2.1-RELEASE</version>
</dependency>

二、配置shiro

创建ShiroConfig类配置shiro信息

@Configuration
public class ShiroConfig {

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        // 自定义拦截器 不进行代码展示,可以使用shiro原拦截器,自行百度
        Map<String, Filter> filters = new HashMap<>();
        filters.put("authc", new AuthJwtFilter());
        shiroFilter.setFilters(filters);

        // 注意过滤器配置顺序 不能颠倒
        // 置不会被拦截的链接 顺序判断
        Map<String, String> filterMap = new LinkedHashMap<String, String>();
        filterMap.put("/captcha", "anon");
        filterMap.put("/login", "anon");
        filterMap.put("/**", "authc");//全验证
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }

    // SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
    @Bean(name = "securityManager")
    public SecurityManager securityManager(AuthRealm authRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(authRealm);

        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager(0, null, 0, 0, null));

        // 关闭Shiro自带的session 使用了JWT
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

    public RedisManager redisManager(int currentDatabase, String currentHost, int currentPort, int currentTimeout, String currentPassword) {
        CustomRedisManager redisManager = new CustomRedisManager();
        redisManager.setDatabase(currentDatabase);
        redisManager.setHost(currentHost);
        redisManager.setPort(currentPort);
        redisManager.setTimeout(currentTimeout);
        redisManager.setPassword(currentPassword);
        return redisManager;
    }

    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager(@Value("${spring.redis.database}") int currentDatabase,
                                          @Value("${spring.redis.host}") String currentHost,
                                          @Value("${spring.redis.port}") int currentPort,
                                          @Value("${spring.redis.timeout}") int currentTimeout,
                                          @Value("${spring.redis.password}") String currentPassword) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager(currentDatabase, currentHost, currentPort, currentTimeout, currentPassword));
        return redisCacheManager;
    }

    /**
     * 创建安全管理器
     */
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(AuthRealm authRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(authRealm);
        return securityManager;
    }

    /**
     * 开启Shiro注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(
            DefaultWebSecurityManager defaultWebSecurityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public AuthRealm authRealm() {
        return new AuthRealm();
    }

    @Bean
    public EventBus eventBus() {
        return new DefaultEventBus();
    }
}

三、realm配置

@Slf4j
@Component
public class AuthRealm extends AuthorizingRealm {
    @Autowired
    private RedisCache redisCache;

    // 必须重写,不然会报错
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof AuthenticationToken;
    }

    /**
     * 授权,首次登录后将缓存数据存入redis,每次请求登录不会在重新授权,只有当redis授权数据失效后才会重新加载授权方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("开始执行授权操作!!!!!");

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        CacheUserDto userDto = (CacheUserDto) principals.getPrimaryPrincipal();
        Integer roleId = userDto.getRoleId();

        // 赋予角色
        Role roleApiOne = roleApi.getOne(roleId);
        if (ObjectUtil.isNotEmpty(roleApiOne)) {
            authorizationInfo.addRole(roleApiOne.getRoleName());
        }

        // 赋予权限
        //TODO 详细代码省略
        //查询角色和权限关联信息集合
        SelRolePermissionDto rolePermission = new SelRolePermissionDto();
        rolePermission.setRoleId(roleId);
        List<RolePermission> rolePermissions = rolePermissionApi.getList(rolePermission);
        if (ObjectUtil.isEmpty(rolePermissions)) {
            return authorizationInfo;
        }
        //查询权限菜单集合
        List<Menu> menus = menuApi.busGetList(menu);
        // 将查询到的menuCode添加到用户权限信息中
        if (ObjectUtil.isNotEmpty(menus)) {
            Set<String> permissionSet = new HashSet<>();
            for (Menu m : menus) {
                permissionSet.add(m.getMenuCode());
            }
            /*
              常见的方法:
              addRole(String role):向授权信息中添加角色。
              addRoles(Collection<String> roles):向授权信息中添加多个角色。
              addStringPermission(String permission):向授权信息中添加权限(字符串形式)。
              addStringPermissions(Collection<String> permissions):向授权信息中添加多个权限(字符串形式)。
              setRoles(Set<String> roles):设置授权信息的角色集合。
              setStringPermissions(Set<String> permissions):设置授权信息的权限集合。
              getRoles():获取授权信息中的角色集合。
              getStringPermissions():获取授权信息中的权限集合。
             */
            authorizationInfo.setStringPermissions(permissionSet);
        }
        return authorizationInfo;
    }

    /**
     * 认证信息
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String userIp = IPAddressUtils.getIpAdrress(request);
        System.out.println("用户请求ip地址:" + userIp);
        String token = (String) authenticationToken.getCredentials();

        // 校验token是否过期
        Claims claim = JwtToken.getClaimByToken(token);
        if (claim == null || JwtToken.isTokenExpired(claim.getExpiration())) {
            // token过期处理机制,目前过期后重新登录
            throw new ExpiredCredentialsException("凭证已过期,请重新登录");
        }

        String jwtSubject = claim.getSubject();

        TokenPayload tokenPayload = JSONUtil.toBean(JSONUtil.toJsonStr(jwtSubject), TokenPayload.class);
        int userId = tokenPayload.getUserId();
        String uuid = tokenPayload.getUuid();
        String key = RedisUtil.getWebUserKey(userId, uuid);
        CacheUserDto user = BeanUtil.toBean(redisCache.get(key), CacheUserDto.class);

        if (user == null) {
            clearCacheUser(key, token);
            throw new ExpiredCredentialsException("登录凭证已失效,请重新登录!");
        }
        if (!user.getToken().equals(token)) {
            clearCacheUser(key, token);
            throw new ExpiredCredentialsException("登录凭证不一致,请重新登录!");
        }

        return new SimpleAuthenticationInfo(user, token, this.getName());
    }

    /**
     * 清除Redis缓存中用户信息
     *
     * @param key
     * @param item
     */
    private void clearCacheUser(String key, String item) {
        redisCache.hdel(key, item);
    }

    /**
     * 重写 获取缓存名
     */
    @Override
    public String getAuthorizationCacheName() {
        return "";
    }
}

四、自定义RedisManager

/**
 * 自定义org.crazycake.shiro.RedisManager
 * 该自定义的Manager扩展的功能:
 * 1.增加database参数,可跟随配置文件的数据库索引选择redis数据库保存相关数据,避免了多个项目共用一个redis database所潜在的问题
 * 2.修改expire参数,默认值为30天 = 2592000s
 */
public class CustomRedisManager extends RedisManager {
    private static JedisPool jedisPool = null;
    private String host = "127.0.0.1";
    private int port = 6379;
    private int expire = 2592000;
    private int timeout = 2000;
    private int database = 0;
    private String password = null;

    public CustomRedisManager() {
    }

    @Override
    public void init() {
        this.password = StringUtils.isEmpty(this.password) ? null : this.password;
        if (jedisPool == null) {
            jedisPool = new JedisPool(new JedisPoolConfig(), this.host, this.port, this.timeout, this.password, database);
        }
    }

    @Override
    public byte[] get(byte[] key) {
        byte[] value = null;

        try (Jedis jedis = jedisPool.getResource()) {
            value = jedis.get(key);
        }

        return value;
    }

    @Override
    public byte[] set(byte[] key, byte[] value) {

        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set(key, value);
            if (this.expire != 0) {
                jedis.expire(key, this.expire);
            }
        }

        return value;
    }

    @Override
    public byte[] set(byte[] key, byte[] value, int expire) {

        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set(key, value);
            if (expire != 0) {
                jedis.expire(key, expire);
            }
        }

        return value;
    }

    @Override
    public void del(byte[] key) {

        try (Jedis jedis = jedisPool.getResource()) {
            jedis.del(key);
        }

    }

    @Override
    public void flushDB() {

        try (Jedis jedis = jedisPool.getResource()) {
            jedis.flushDB();
        }

    }

    @Override
    public Long dbSize() {
        Long dbSize = 0L;

        try (Jedis jedis = jedisPool.getResource()) {
            dbSize = jedis.dbSize();
        }

        return dbSize;
    }

    @Override
    public Set<byte[]> keys(String pattern) {
        Set<byte[]> keys = null;

        try (Jedis jedis = jedisPool.getResource()) {
            keys = jedis.keys(pattern.getBytes());
        }

        return keys;
    }

    @Override
    public String getHost() {
        return this.host;
    }

    @Override
    public void setHost(String host) {
        this.host = host;
    }

    @Override
    public int getPort() {
        return this.port;
    }

    @Override
    public void setPort(int port) {
        this.port = port;
    }

    @Override
    public int getExpire() {
        return this.expire;
    }

    @Override
    public void setExpire(int expire) {
        this.expire = expire;
    }

    @Override
    public int getTimeout() {
        return this.timeout;
    }

    @Override
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public void setPassword(String password) {
        this.password = password;
    }

    public int getDatabase() {
        return database;
    }

    public void setDatabase(Integer database) {
        if (null == database) {
            return;
        }
        this.database = database;
    }

}

五、使用注解

@RequiresAuthentication 表示subject已经通过登录验证,才可使用
@RequiresUser 表示subject已经身份验证或者通过记住我登录,才可使用
@RequiresGuest 表示subject没有身份验证或通过记住我登录过,即是游客身份,才可使用
@RequiresRoles(value={“admin”, “user”}, logical=Logical.AND) 表示subject需要xx(value)角色,才可使用
@RequiresPermissions (value={“user:a”, “user:b”},logical= Logical.OR) 表示subject需要xxx(value)权限,才可使用

    @RequiresRoles(value = {"admin", "管理员"}, logical = Logical.OR)
    @RequiresPermissions(value = {"sys:man:role"}, logical = Logical.AND)
    @RequestMapping(value = "/getList", method = RequestMethod.POST)
    public Result getList(@RequestBody SelRoleDto role) throws Exception {
    
    }

六、捕捉异常

根据自身需求处理异常,我这里使用了全局捕获

    @ExceptionHandler(UnauthorizedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public Result handleUnauthorizedException(HttpServletRequest request, UnauthorizedException e) {
        // 在这里定义返回的信息或者跳转到指定的页面
        log.error("无权限访问!!! {}", e.getMessage());
        return Result.error("无权限访问");
    }
  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tan.]der

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值