springboot2整合shiro

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013219624/article/details/83588583

0.准备

SQL使用flyway管理, DAO层是用mybatis, 缓存使用Redis, 具体看源码(本章末尾会贴出).下面主要是shiro部分

1.添加Maven依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>

2.自定义SpringbootDemoAuthorizingRealm继承AuthorizingRealm

public class SpringbootDemoAuthorizingRealm extends AuthorizingRealm {
    @Autowired
    private IUserService userServiceImpl;
    @Autowired
    private IRoleService roleServiceImpl;
    @Autowired
    private IPermissionService permissionServiceImpl;

    @Override
    public String getName() {
        return "SpringbootDemoAuthorizingRealm";
    }
    
    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        String password = new String(token.getPassword());

        if (StringUtils.isBlank(username)) {
            throw new UnknownAccountException();
        }
        if (StringUtils.isBlank(password)) {
            throw new IncorrectCredentialsException();
        }

        User user = userServiceImpl.getUserByUsername(username);
        if (Objects.isNull(user)) {
            throw new UnknownAccountException();
        }

        SimpleUser simpleUser = new SimpleUser();
        simpleUser.setId(user.getId());
        simpleUser.setUsername(user.getUsername());

        // 清除授权信息
        clearAuthorizationInfoCache(simpleUser);
        return new SimpleAuthenticationInfo(simpleUser, user.getPassword(), ByteSource.Util.bytes(username), this.getName());
    }

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleUser simpleUser = (SimpleUser) principalCollection.getPrimaryPrincipal();
        List<Role> roleList = roleServiceImpl.getRoleListByUserId(simpleUser.getId());

        List<String> simpleRoles = new ArrayList<>(roleList.size());
        List<Long> roleIds = new ArrayList<>(roleList.size());
        for (Role role : roleList) {
            simpleRoles.add(role.getRole());
            roleIds.add(role.getId());
        }
        List<String> permissions = permissionServiceImpl.getPermissionsByRoleIds(roleIds);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(simpleRoles);
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

    /**
     * 清除所有授权信息
     */
    public void clearAuthorizationInfoCache() {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if(Objects.nonNull(cache)) {
            cache.clear();
        }
    }


    private void clearAuthorizationInfoCache(SimpleUser simpleUser) {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        cache.remove(simpleUser.getId());
    }
}

3.自定义ShiroConfig

@Configuration
public class ShiroConfig {
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(3);
        return hashedCredentialsMatcher;
    }

    @Bean
    public SpringbootDemoAuthorizingRealm springbootDemoAuthorizingRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
        SpringbootDemoAuthorizingRealm realm = new SpringbootDemoAuthorizingRealm();
        realm.setCredentialsMatcher(hashedCredentialsMatcher);
        // 启用缓存
        realm.setCachingEnabled(true);
        // 启用认证缓存
        realm.setAuthorizationCachingEnabled(true);
        // 启用授权缓存
        realm.setAuthorizationCachingEnabled(true);
        return realm;
    }

    @Bean
    public SecurityManager securityManager(SpringbootDemoAuthorizingRealm springbootDemoAuthorizingRealm, SessionManager sessionManager, ShiroCacheManager shiroCacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(springbootDemoAuthorizingRealm);
        securityManager.setSessionManager(sessionManager);
        securityManager.setCacheManager(shiroCacheManager);
        return securityManager;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/");
        shiroFilterFactoryBean.setUnauthorizedUrl("/forbidden.do");

        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 登录不拦截
        filterChainDefinitionMap.put("/login.do", "anon");
        // 其他接口都必须登录
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        // 跳转页面删除sessionId
        defaultWebSessionManager.setSessionIdUrlRewritingEnabled(false);
        // 30分钟
        defaultWebSessionManager.setGlobalSessionTimeout(1800000);
        defaultWebSessionManager.setDeleteInvalidSessions(true);
        defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
        defaultWebSessionManager.setSessionIdCookieEnabled(true);
        return defaultWebSessionManager;
    }
}

4.自定义ShiroCacheManager, 使用Redis作为Shiro缓存

@Component
public class ShiroCacheManager implements CacheManager {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        return new ShiroRedisCache<>(name);
    }

    public class ShiroRedisCache<K,V> implements Cache<K,V> {

        private String cacheKey;

        private ShiroRedisCache(String cacheKey) {
            this.cacheKey = cacheKey;
        }

        private Object hashKey(K key) {
            if(key instanceof PrincipalCollection) {
                PrincipalCollection principalCollection = (PrincipalCollection) key;
                SimpleUser simpleUser = (SimpleUser) principalCollection.getPrimaryPrincipal();
                return simpleUser.getId();
            }
            return key;
        }

        @Override
        public V get(K key) throws CacheException {
            BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
            Object realKey = hashKey(key);
            return boundHashOperations.get(realKey);
        }

        @Override
        public V put(K key, V value) throws CacheException {
            BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
            Object realKey = hashKey(key);
            boundHashOperations.put((K) realKey, value);
            return value;
        }

        @Override
        public V remove(K key) throws CacheException {
            BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
            Object realKey = hashKey(key);
            V value = boundHashOperations.get(realKey);
            boundHashOperations.delete(realKey);
            return value;
        }

        @Override
        public void clear() throws CacheException {
            redisTemplate.delete(cacheKey);
        }

        @Override
        public int size() {
            BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
            return boundHashOperations.size().intValue();
        }

        @Override
        public Set<K> keys() {
            BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
            return boundHashOperations.keys();
        }

        @Override
        public Collection<V> values() {
            BoundHashOperations<String, K, V> boundHashOperations = redisTemplate.boundHashOps(cacheKey);
            return boundHashOperations.values();
        }
    }
}

5.使用

@RestController
public class LoginController {

    @RequestMapping(value="", method = RequestMethod.GET)
    public ResultMessage defaultPath(){
        return ResultMessage.fail("请先登录");
    }

    @RequestMapping(value="/forbidden.do", method = RequestMethod.GET)
    public ResultMessage forbidden(){
        return ResultMessage.fail("没有权限");
    }

    @RequestMapping(value="/login.do", method = RequestMethod.POST)
    public ResultMessage login(@RequestBody UserRequest request){
        UsernamePasswordToken token = new UsernamePasswordToken(request.getUsername(), request.getPassword(),false);
        Subject currentUser = SecurityUtils.getSubject();
        try {
            currentUser.login(token);
        } catch (UnknownAccountException e) {
            throw new UnknownAccountException("账号不存在");
        } catch (IncorrectCredentialsException e) {
            throw new IncorrectCredentialsException("密码不匹配");
        } catch (LockedAccountException e) {
            throw new LockedAccountException("账号被锁定");
        } catch (AuthenticationException e) {
            throw new AuthenticationException("其他认证错误");
        } catch (RuntimeException e) {
            throw new RuntimeException("登录未知错误");
        }
        return ResultMessage.success("登录成功");
    }

    @RequestMapping(value="/logout.do", method =RequestMethod.GET)
    public ResultMessage logout(){
        try {
            SecurityUtils.getSubject().logout();
        } catch (Exception e) {
            throw new RuntimeException("注销未知错误");
        }
        return ResultMessage.success("注销成功");
    }

    @RequestMapping(value="/other.do", method =RequestMethod.GET)
    public ResultMessage other(){
        return ResultMessage.success("other success.");
    }

    @RequestMapping(value="/addTeacher.do", method =RequestMethod.GET)
    @RequiresPermissions({"teacher:add"})
    public ResultMessage addTeacher(){
        return ResultMessage.success("permission success.");
    }

    @RequestMapping(value="/updateTeacher.do", method =RequestMethod.GET)
    @RequiresPermissions({"teacher:update"})
    public ResultMessage updateTeacher(){
        return ResultMessage.success("permission success.");
    }

    /**
     * 统一异常处理
     */
    @ExceptionHandler(RuntimeException.class)
    public ResultMessage exception(Exception ex) {
        if (ex instanceof AuthorizationException) {
            return ResultMessage.fail("未授权");
        }
        return ResultMessage.fail(ex.getMessage());
    }
}

6.总结

shiro相对比较简单, 认证(可以理解为登录), 授权(就是权限判断), 会话管理, 加密等模块. 

下一节会讲如何重写注解RequiresPermissions, 并将菜单入库.

源码 https://gitee.com/jsjack_wang/springboot-demo dev-shiro分支

猜你喜欢

转载自blog.csdn.net/u013219624/article/details/83588583