GVKun编程网logo

SpringBoot集成Shiro 实现动态加载权限(springboot动态加载配置)

20

关于SpringBoot集成Shiro实现动态加载权限和springboot动态加载配置的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于Apollo—与springboot集成实现动态刷新

关于SpringBoot集成Shiro 实现动态加载权限springboot动态加载配置的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于Apollo — 与springboot集成实现动态刷新、shiro 整合 springboot 实现动态授权、Shiro(二)通过shiro实现登录 连接数据库+集成Springboot、spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制等相关知识的信息别忘了在本站进行查找喔。

本文目录一览:

SpringBoot集成Shiro 实现动态加载权限(springboot动态加载配置)

SpringBoot集成Shiro 实现动态加载权限(springboot动态加载配置)

一、前言

本文小编将基于 SpringBoot 集成 Shiro 实现动态uri权限,由前端vue在页面配置uri,Java后端动态刷新权限,不用重启项目,以及在页面分配给用户 角色按钮uri 权限后,后端动态分配权限,用户无需在页面重新登录才能获取最新权限,一切权限动态加载,灵活配置

基本环境
  1. spring-boot 2.1.7
  2. mybatis-plus 2.1.0
  3. mysql 5.7.24
  4. redis 5.0.5

温馨小提示:案例demo源码附文章末尾,有需要的小伙伴们可参考哦 ~

二、SpringBoot集成Shiro

1、引入相关maven依赖

<properties>
	<shiro-spring.version>1.4.0</shiro-spring.version>
    <shiro-redis.version>3.1.0</shiro-redis.version>
</properties>
<dependencies>
    <!-- AOP依赖,一定要加,否则权限拦截验证不生效 【注:系统日记也需要此依赖】 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    <!-- Shiro 核心依赖 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>${shiro-spring.version}</version>
    </dependency>
    <!-- Shiro-redis插件 -->
    <dependency>
        <groupId>org.crazycake</groupId>
        <artifactId>shiro-redis</artifactId>
        <version>${shiro-redis.version}</version>
    </dependency>
</dependencies>

2、自定义Realm

  1. doGetAuthenticationInfo:身份认证 (主要是在登录时的逻辑处理)
  2. doGetAuthorizationInfo:登陆认证成功后的处理 ex: 赋予角色和权限 【 注:用户进行权限验证时 Shiro会去缓存中找,如果查不到数据,会执行doGetAuthorizationInfo这个方法去查权限,并放入缓存中 】 -> 因此我们在前端页面分配用户权限时 执行清除shiro缓存的方法即可实现动态分配用户权限
@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private MenuMapper menuMapper;
    @Autowired
    private RoleMapper roleMapper;

    @Override
    public String getName() {
        return "shiroRealm";
    }

    /**
     * 赋予角色和权限:用户进行权限验证时 Shiro会去缓存中找,如果查不到数据,会执行这个方法去查权限,并放入缓存中
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 获取用户
        User user = (User) principalCollection.getPrimaryPrincipal();
        Integer userId =user.getId();
        // 这里可以进行授权和处理
        Set<String> rolesSet = new HashSet<>();
        Set<String> permsSet = new HashSet<>();
        // 获取当前用户对应的权限(here根据业务自行查询)
        List<Role> roleList = roleMapper.selectRoleByUserId( userId );
        for (Role role:roleList) {
            rolesSet.add( role.getCode() );
            List<Menu> menuList = menuMapper.selectMenuByRoleId( role.getId() );
            for (Menu menu :menuList) {
                permsSet.add( menu.getResources() );
            }
        }
        //将查到的权限和角色分别传入authorizationInfo中
        authorizationInfo.setStringPermissions(permsSet);
        authorizationInfo.setRoles(rolesSet);
        log.info("--------------- 赋予角色和权限成功! ---------------");
        return authorizationInfo;
    }

    /**
     * 身份认证 - 之后走上面的 授权
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken tokenInfo = (UsernamePasswordToken)authenticationToken;
        // 获取用户输入的账号
        String username = tokenInfo.getUsername();
        // 获取用户输入的密码
        String password = String.valueOf( tokenInfo.getPassword() );

        // 通过username从数据库中查找 User对象,如果找到进行验证
        // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        User user = userMapper.selectUserByUsername(username);
        // 判断账号是否存在
        if (user == null) {
            //返回null -> shiro就会知道这是用户不存在的异常
            return null;
        }
        // 验证密码 【注:这里不采用shiro自身密码验证 , 采用的话会导致用户登录密码错误时,已登录的账号也会自动下线!  如果采用,移除下面的清除缓存到登录处 处理】
        if ( !password.equals( user.getPwd() ) ){
            throw new IncorrectCredentialsException("用户名或者密码错误");
        }

        // 判断账号是否被冻结
        if (user.getFlag()==null|| "0".equals(user.getFlag())){
            throw new LockedAccountException();
        }
        /**
         * 进行验证 -> 注:shiro会自动验证密码
         * 参数1:principal -> 放对象就可以在页面任意地方拿到该对象里面的值
         * 参数2:hashedCredentials -> 密码
         * 参数3:credentialsSalt -> 设置盐值
         * 参数4:realmName -> 自定义的Realm
         */
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
        // 验证成功开始踢人(清除缓存和Session)
        ShiroUtils.deleteCache(username,true);

        // 认证成功后更新token
        String token = ShiroUtils.getSession().getId().toString();
        user.setToken( token );
        userMapper.updateById(user);
        return authenticationInfo;
    }

}

3、Shiro配置类

@Configuration
public class ShiroConfig {

    private final String CACHE_KEY = "shiro:cache:";
    private final String SESSION_KEY = "shiro:session:";
    /**
     * 默认过期时间30分钟,即在30分钟内不进行操作则清空缓存信息,页面即会提醒重新登录
     */
    private final int EXPIRE = 1800;

    /**
     *  Redis配置
     */
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
//    @Value("${spring.redis.password}")
//    private String password;

    /**
     * 开启Shiro-aop注解支持:使用代理方式所以需要开启代码支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * Shiro基础配置
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager, ShiroServiceImpl shiroConfig){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 自定义过滤器
        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        // 定义过滤器名称 【注:map里面key值对于的value要为authc才能使用自定义的过滤器】
        filtersMap.put( "zqPerms", new MyPermissionsAuthorizationFilter() );
        filtersMap.put( "zqRoles", new MyRolesAuthorizationFilter() );
        filtersMap.put( "token", new TokenCheckFilter() );
        shiroFilterFactoryBean.setFilters(filtersMap);

        // 登录的路径: 如果你没有登录则会跳到这个页面中 - 如果没有设置值则会默认跳转到工程根目录下的"/login.jsp"页面 或 "/login" 映射
        shiroFilterFactoryBean.setLoginUrl("/api/auth/unLogin");
        // 登录成功后跳转的主页面 (这里没用,前端vue控制了跳转)
//        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 设置没有权限时跳转的url
        shiroFilterFactoryBean.setUnauthorizedUrl("/api/auth/unauth");

        shiroFilterFactoryBean.setFilterChainDefinitionMap( shiroConfig.loadFilterChainDefinitionMap() );
        return shiroFilterFactoryBean;
    }

    /**
     * 安全管理器
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 自定义session管理
        securityManager.setSessionManager(sessionManager());
        // 自定义Cache实现缓存管理
        securityManager.setCacheManager(cacheManager());
        // 自定义Realm验证
        securityManager.setRealm(shiroRealm());
        return securityManager;
    }

    /**
     * 身份验证器
     */
    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return shiroRealm;
    }

    /**
     *  自定义Realm的加密规则 -> 凭证匹配器:将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法:这里使用SHA256算法;
        shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
        // 散列的次数,比如散列两次,相当于 md5(md5(""));
        shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
        return shaCredentialsMatcher;
    }

    /**
     * 配置Redis管理器:使用的是shiro-redis开源插件
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setTimeout(timeout);
//        redisManager.setPassword(password);
        return redisManager;
    }

    /**
     * 配置Cache管理器:用于往Redis存储权限和角色标识  (使用的是shiro-redis开源插件)
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setKeyPrefix(CACHE_KEY);
        // 配置缓存的话要求放在session里面的实体类必须有个id标识 注:这里id为用户表中的主键,否-> 报:User must has getter for field: xx
        redisCacheManager.setPrincipalIdFieldName("id");
        return redisCacheManager;
    }

    /**
     * SessionID生成器
     */
    @Bean
    public ShiroSessionIdGenerator sessionIdGenerator(){
        return new ShiroSessionIdGenerator();
    }

    /**
     * 配置RedisSessionDAO (使用的是shiro-redis开源插件)
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        redisSessionDAO.setKeyPrefix(SESSION_KEY);
        redisSessionDAO.setExpire(EXPIRE);
        return redisSessionDAO;
    }

    /**
     * 配置Session管理器
     */
    @Bean
    public SessionManager sessionManager() {
        ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
        shiroSessionManager.setSessionDAO(redisSessionDAO());
        return shiroSessionManager;
    }

}

三、shiro动态加载权限处理方法

  1. loadFilterChainDefinitionMap:初始化权限 ex: 在上面Shiro配置类ShiroConfig中的Shiro基础配置shiroFilterFactory方法中我们就需要调用此方法将数据库中配置的所有uri权限全部加载进去,以及放行接口和配置权限过滤器等 【注:过滤器配置顺序不能颠倒,多个过滤器用 , 分割】
    ex: filterChainDefinitionMap.put("/api/system/user/list", "authc,token,zqPerms[user1]")
  2. updatePermission:动态刷新加载数据库中的uri权限 -> 页面在新增uri路径到数据库中,也就是配置新的权限时就可以调用此方法实现动态加载uri权限
  3. updatePermissionByRoleId:shiro动态权限加载 -> 即分配指定用户权限时可调用此方法删除shiro缓存,重新执行doGetAuthorizationInfo方法授权角色和权限
public interface ShiroService {

    /**
     * 初始化权限 -> 拿全部权限
     *
     * @param :
     * @return: java.util.Map<java.lang.String,java.lang.String>
     */
    Map<String, String> loadFilterChainDefinitionMap();

    /**
     * 在对uri权限进行增删改操作时,需要调用此方法进行动态刷新加载数据库中的uri权限
     *
     * @param shiroFilterFactoryBean
     * @param roleId
     * @param isRemoveSession:
     * @return: void
     */
    void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession);

    /**
     * shiro动态权限加载 -> 原理:删除shiro缓存,重新执行doGetAuthorizationInfo方法授权角色和权限
     *
     * @param roleId
     * @param isRemoveSession:
     * @return: void
     */
    void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession);

}
@Slf4j
@Service
public class ShiroServiceImpl implements ShiroService {

    @Autowired
    private MenuMapper menuMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;

    @Override
    public Map<String, String> loadFilterChainDefinitionMap() {
        // 权限控制map
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 配置过滤:不会被拦截的链接 -> 放行 start ----------------------------------------------------------
        // 放行Swagger2页面,需要放行这些
        filterChainDefinitionMap.put("/swagger-ui.html","anon");
        filterChainDefinitionMap.put("/swagger/**","anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**","anon");
        filterChainDefinitionMap.put("/v2/**","anon");
        filterChainDefinitionMap.put("/static/**", "anon");

        // 登陆
        filterChainDefinitionMap.put("/api/auth/login/**", "anon");
        // 三方登录
        filterChainDefinitionMap.put("/api/auth/loginByQQ", "anon");
        filterChainDefinitionMap.put("/api/auth/afterlogin.do", "anon");
        // 退出
        filterChainDefinitionMap.put("/api/auth/logout", "anon");
        // 放行未授权接口,重定向使用
        filterChainDefinitionMap.put("/api/auth/unauth", "anon");
        // token过期接口
        filterChainDefinitionMap.put("/api/auth/tokenExpired", "anon");
        // 被挤下线
        filterChainDefinitionMap.put("/api/auth/downline", "anon");
        // 放行 end ----------------------------------------------------------

        // 从数据库或缓存中查取出来的url与resources对应则不会被拦截 放行
        List<Menu> permissionList = menuMapper.selectList( null );
        if ( !CollectionUtils.isEmpty( permissionList ) ) {
            permissionList.forEach( e -> {
                if ( StringUtils.isNotBlank( e.getUrl() ) ) {
                    // 根据url查询相关联的角色名,拼接自定义的角色权限
                    List<Role> roleList = roleMapper.selectRoleByMenuId( e.getId() );
                    StringJoiner zqRoles = new StringJoiner(",", "zqRoles[", "]");
                    if ( !CollectionUtils.isEmpty( roleList ) ){
                        roleList.forEach( f -> {
                            zqRoles.add( f.getCode() );
                        });
                    }

                    // 注意过滤器配置顺序不能颠倒
                    // ① 认证登录
                    // ② 认证自定义的token过滤器 - 判断token是否有效
                    // ③ 角色权限 zqRoles:自定义的只需要满足其中一个角色即可访问  ;  roles[admin,guest] : 默认需要每个参数满足才算通过,相当于hasAllRoles()方法
                    // ④ zqPerms:认证自定义的url过滤器拦截权限  【注:多个过滤器用 , 分割】
//                    filterChainDefinitionMap.put( "/api" + e.getUrl(),"authc,token,roles[admin,guest],zqPerms[" + e.getResources() + "]" );
                    filterChainDefinitionMap.put( "/api" + e.getUrl(),"authc,token,"+ zqRoles.toString() +",zqPerms[" + e.getResources() + "]" );
//                        filterChainDefinitionMap.put("/api/system/user/listPage", "authc,token,zqPerms[user1]"); // 写死的一种用法
                }
            });
        }
        // ⑤ 认证登录  【注:map不能存放相同key】
        filterChainDefinitionMap.put("/**", "authc");
        return filterChainDefinitionMap;
    }

    @Override
    public void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession) {
        synchronized (this) {
            AbstractShiroFilter shiroFilter;
            try {
                shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();
            } catch (Exception e) {
                throw new MyException("get ShiroFilter from shiroFilterFactoryBean error!");
            }
            PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();
            DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();

            // 清空拦截管理器中的存储
            manager.getFilterChains().clear();
            // 清空拦截工厂中的存储,如果不清空这里,还会把之前的带进去
            //            ps:如果仅仅是更新的话,可以根据这里的 map 遍历数据修改,重新整理好权限再一起添加
            shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
            // 动态查询数据库中所有权限
            shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitionMap());
            // 重新构建生成拦截
            Map<String, String> chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                manager.createChain(entry.getKey(), entry.getValue());
            }
            log.info("--------------- 动态生成url权限成功! ---------------");

            // 动态更新该角色相关联的用户shiro权限
            if(roleId != null){
                updatePermissionByRoleId(roleId,isRemoveSession);
            }
        }
    }

    @Override
    public void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession) {
        // 查询当前角色的用户shiro缓存信息 -> 实现动态权限
        List<User> userList = userMapper.selectUserByRoleId(roleId);
        // 删除当前角色关联的用户缓存信息,用户再次访问接口时会重新授权 ; isRemoveSession为true时删除Session -> 即强制用户退出
        if ( !CollectionUtils.isEmpty( userList ) ) {
            for (User user : userList) {
                ShiroUtils.deleteCache(user.getUsername(), isRemoveSession);
            }
        }
        log.info("--------------- 动态修改用户权限成功! ---------------");
    }

}

四、shiro中自定义角色、权限过滤器

1、自定义uri权限过滤器 zqPerms

@Slf4j
public class MyPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String requestUrl = httpRequest.getServletPath();
        log.info("请求的url:  " + requestUrl);

        // 检查是否拥有访问权限
        Subject subject = this.getSubject(request, response);
        if (subject.getPrincipal() == null) {
            this.saveRequestAndRedirectToLogin(request, response);
        } else {
            // 转换成http的请求和响应
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse resp = (HttpServletResponse) response;

            // 获取请求头的值
            String header = req.getHeader("X-Requested-With");
            // ajax 的请求头里有X-Requested-With: XMLHttpRequest      正常请求没有
            if (header!=null && "XMLHttpRequest".equals(header)){
                resp.setContentType("text/json,charset=UTF-8");
                resp.getWriter().print("{\"success\":false,\"msg\":\"没有权限操作!\"}");
            }else {  //正常请求
                String unauthorizedUrl = this.getUnauthorizedUrl();
                if (StringUtils.hasText(unauthorizedUrl)) {
                    WebUtils.issueRedirect(request, response, unauthorizedUrl);
                } else {
                    WebUtils.toHttp(response).sendError(401);
                }
            }

        }
        return false;
    }
    
}

2、自定义角色权限过滤器 zqRoles

shiro原生的角色过滤器RolesAuthorizationFilter 默认是必须同时满足roles[admin,guest]才有权限,而自定义的zqRoles 只满足其中一个即可访问
ex: zqRoles[admin,guest]

public class MyRolesAuthorizationFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception {
        Subject subject = getSubject(req, resp);
        String[] rolesArray = (String[]) mappedValue;
        // 没有角色限制,有权限访问
        if (rolesArray == null || rolesArray.length == 0) {
            return true;
        }
        for (int i = 0; i < rolesArray.length; i++) {
            //若当前用户是rolesArray中的任何一个,则有权限访问
            if (subject.hasRole(rolesArray[i])) {
                return true;
            }
        }
        return false;
    }

}

3、自定义token过滤器 token -> 判断token是否过期失效等

@Slf4j
public class TokenCheckFilter extends UserFilter {

    /**
     * token过期、失效
     */
    private static final String TOKEN_EXPIRED_URL = "/api/auth/tokenExpired";

    /**
     * 判断是否拥有权限 true:认证成功  false:认证失败
     * mappedValue 访问该url时需要的权限
     * subject.isPermitted 判断访问的用户是否拥有mappedValue权限
     */
    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        // 根据请求头拿到token
        String token = WebUtils.toHttp(request).getHeader(Constants.REQUEST_HEADER);
        log.info("浏览器token:" + token );
        User userInfo = ShiroUtils.getUserInfo();
        String userToken = userInfo.getToken();
        // 检查token是否过期
        if ( !token.equals(userToken) ){
            return false;
        }
        return true;
    }

    /**
     * 认证失败回调的方法: 如果登录实体为null就保存请求和跳转登录页面,否则就跳转无权限配置页面
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        User userInfo = ShiroUtils.getUserInfo();
        // 重定向错误提示处理 - 前后端分离情况下
        WebUtils.issueRedirect(request, response, TOKEN_EXPIRED_URL);
        return false;
    }

}

五、项目中会用到的一些工具类、常量等

温馨小提示:这里只是部分,详情可参考文章末尾给出的案例demo源码

1、Shiro工具类

public class ShiroUtils {

	/** 私有构造器 **/
	private ShiroUtils(){ }

    private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);

    /**
     * 获取当前用户Session
     * @Return SysUserEntity 用户信息
     */
    public static Session getSession() {
        return SecurityUtils.getSubject().getSession();
    }

    /**
     * 用户登出
     */
    public static void logout() {
        SecurityUtils.getSubject().logout();
    }

	/**
	 * 获取当前用户信息
	 * @Return SysUserEntity 用户信息
	 */
	public static User getUserInfo() {
		return (User) SecurityUtils.getSubject().getPrincipal();
	}

    /**
     * 删除用户缓存信息
     * @Param  username  用户名称
     * @Param  isRemoveSession 是否删除Session,删除后用户需重新登录
     */
    public static void deleteCache(String username, boolean isRemoveSession){
        //从缓存中获取Session
        Session session = null;
        // 获取当前已登录的用户session列表
        Collection<Session> sessions = redisSessionDAO.getActiveSessions();
        User sysUserEntity;
        Object attribute = null;
        // 遍历Session,找到该用户名称对应的Session
        for(Session sessionInfo : sessions){
            attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (attribute == null) {
                continue;
            }
            sysUserEntity = (User) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
            if (sysUserEntity == null) {
                continue;
            }
            if (Objects.equals(sysUserEntity.getUsername(), username)) {
                session=sessionInfo;
                // 清除该用户以前登录时保存的session,强制退出  -> 单用户登录处理
                if (isRemoveSession) {
                    redisSessionDAO.delete(session);
                }
            }
        }

        if (session == null||attribute == null) {
            return;
        }
        //删除session
        if (isRemoveSession) {
            redisSessionDAO.delete(session);
        }
        //删除Cache,再访问受限接口时会重新授权
        DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
        Authenticator authc = securityManager.getAuthenticator();
        ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);
    }

    /**
     * 从缓存中获取指定用户名的Session
     * @param username
     */
    private static Session getSessionByUsername(String username){
        // 获取当前已登录的用户session列表
        Collection<Session> sessions = redisSessionDAO.getActiveSessions();
        User user;
        Object attribute;
        // 遍历Session,找到该用户名称对应的Session
        for(Session session : sessions){
            attribute = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (attribute == null) {
                continue;
            }
            user = (User) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
            if (user == null) {
                continue;
            }
            if (Objects.equals(user.getUsername(), username)) {
                return session;
            }
        }
        return null;
    }

}

2、Redis常量类

public interface RedisConstant {
    /**
     * TOKEN前缀
     */
    String REDIS_PREFIX_LOGIN = "code-generator_token_%s";
}

3、Spring上下文工具类

@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext context;
    /**
     * Spring在bean初始化后会判断是不是ApplicationContextAware的子类
     * 如果该类是,setApplicationContext()方法,会将容器中ApplicationContext作为参数传入进去
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
    /**
     * 通过Name返回指定的Bean
     */
    public static <T> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }
}

六、案例demo源码

GitHub地址

https://github.com/zhengqingya/code-generator/tree/master/code-generator-api/src/main/java/com/zhengqing/modules/shiro

码云地址

https://gitee.com/zhengqingya/code-generator/blob/master/code-generator-api/src/main/java/com/zhengqing/modules/shiro

Apollo — 与springboot集成实现动态刷新

Apollo — 与springboot集成实现动态刷新

apollo与spring实现动态刷新配置,主要有2种刷新

  • 基于普通字段刷新
  • 基于bean上使用了@ConfigurationProperties刷新

一、普通字段刷新

在需刷新的字段上配置@Value注解,如:
@Value("${ve.auth-url}")
private String authUrl;

 

二、bean使用@ConfigurationProperties动态刷新

bean使用@ConfigurationProperties注解目前还不支持自动刷新,需要编写额外的代码。目前官方提供2种刷新方案

  • 基于RefreshScope实现刷新
  • 基于EnvironmentChangeEvent实现刷新

 

项目选用基于RefreshScope实现刷新。

1.bean上使用@RefreshScope注解

@ConfigurationProperties(prefix = "partners")
@Component("partnersConfig")
@RefreshScope
@Slf4j
@Data
public class PartnersConfig {
    private List<FundProvider> fundProviders = Lists.newArrayList();
    private List<LogisticsCompany> logisticsCompanies = Lists.newArrayList();
    private List<InsuranceCompany> insuranceCompanies = Lists.newArrayList();
}

 

2.利用RefreshScope搭配@ApolloConfigChangeListener监听实现bean的动态刷新

其代码实现如下:

@Component
@Slf4j
public class ApolloRefreshConfig {
    private final PartnersConfig partnersConfig;
    private final AnXinSignProperty anXinSignProperty;
    private final RefreshScope refreshScope;
    public ApolloRefreshConfig(final PartnersConfig partnersConfig,
                               final AnXinSignProperty anXinSignProperty,
                               final RefreshScope refreshScope) {
        this.partnersConfig = partnersConfig;
        this.anXinSignProperty = anXinSignProperty;
        this.refreshScope = refreshScope;
    }
    /**
     * 记录配置更改事件
     *
     * @param changeEvent 配置更改事件
     * @return void
     * @author xux
     * @date 2021/4/9
     */
    public static void printChange(ConfigChangeEvent changeEvent) {
        Set<String> changeKeys = changeEvent.changedKeys();
        if (!CollectionUtils.isEmpty(changeKeys)) {
            for (String changeKey : changeKeys) {
                ConfigChange configChange = changeEvent.getChange(changeKey);
                log.info("apollo changed , key:" + changeKey + "; old value:" + configChange.getOldValue() + "; new value:" + configChange.getNewValue());
            }
        }
    }
    /**
     * 监听partner配置更改,value为监听的配置文件
     *
     * @param configChangeEvent 配置更改事件
     * @return void
     * @author xux
     * @date 2021/4/9
     */
    @ApolloConfigChangeListener(value = {"fund-provider-partners.properties", "insurance-companie-partners.properties", "logistics-company-partners.properties"})
    private void refreshPartnersConfig(ConfigChangeEvent configChangeEvent) {
        printChange(configChangeEvent);
        log.info("before refresh {}", partnersConfig.toString());
        refreshScope.refresh("partnersConfig");
        log.info("after refresh {}", partnersConfig.toString());
    }
    /**
     * 监听安心签配置更改,value默认监听application
     *
     * @param configChangeEvent 配置更改事件
     * @return void
     * @author xux
     * @date 2021/4/9
     */
    @ApolloConfigChangeListener(interestedKeyPrefixes = "spring.cfca.anxinsign")
    private void refreshAnXinSignProperty(ConfigChangeEvent configChangeEvent) {
        printChange(configChangeEvent);
        log.info("before refresh {}", anXinSignProperty.toString());
        refreshScope.refresh("anXinSignProperty");
        log.info("after refresh {}", anXinSignProperty.toString());
    }
}

 

shiro 整合 springboot 实现动态授权

shiro 整合 springboot 实现动态授权

前言:参考网上的资料建议不要看的太多,自己整合的时候真的很容易出问题

参考 :

springboot整合shiro     https://www.bilibili.com/video/av40342174?from=search&seid=1037334172984022730

shiro中的动态授权     https://www.jianshu.com/p/22f78f8677f3

-----------------------------------------------分割线----------------------------

首先你要再pom.xml中 加入shiro 的依赖

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

然后 写一个realm类继承 AuthorizingRealm,这个类是需要自己写的,并且将其放入之后写的配置类中,主要是执行授权/鉴权逻辑的地方

public class MyRealm extends AuthorizingRealm {
    @Autowired
    UserDao userDao;
    /**
     * 执行授权逻辑
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行授权逻辑");
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getSession().getAttribute("user");
        //给资源授权
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //添加资源的授权字符串
        //info.addStringPermission("user:info");
        //添加角色,从数据库查
        //info.addRole("6");
        //数据库中多个角色是用 | 分隔
        Set<String> roles = new HashSet<>(Arrays.asList(userDao.selByUser(user.getUserId()).getType().split("\\|")));
        info.setRoles(roles);
        return info;
    }

    /**
     * 执行认证逻辑
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证逻辑");
        //获取数据库的信息
        //编写shiro 的 认证逻辑
        //判断用户名
        UsernamePasswordToken userInfo = (UsernamePasswordToken) token;
        User user = userDao.login(userInfo.getUsername());
        if ( user == null ){
            return null;//shiro会抛出用户名异常
        }
        if ( "1".equals(user.getStatus()) || "2".equals(user.getStatus()) || "3".equals(user.getStatus()) ){
            String str = "" ;
            switch ( user.getStatus() ){
                case "1":str = "退休";break;
                case "2":str = "离职";break;
                case "3":str = "停用";break;
            }
            //用户状态: 0:正常 1:退休 2:离职 3 停用 ,123时抛出锁定异常.不让登陆
            throw new DisabledAccountException("登陆失败,用户状态:"+str);
        }
        //判断密码 参数1:需要返回给login 方法的数据,2:数据库的密码,3:shiro的名字
        return new SimpleAuthenticationInfo("",user.getPassword(),"");
    }
}

(ps:鉴权,授权的逻辑是根据自己的需求写的.

*如果,需求是一个资源需要关联多个角色.并且用户只要有任何一个角色就可以操作的话,需要自定义filter,因为shiro 默认是 当一个资源关联多个角色,用户必须具有所有的角色,才可以操作资源,如果没有这个需求,可以不用自定义filter

public class RolesFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        Subject subject = getSubject(request, response);
        //所有路径 都要判断是否登陆,使用自定义过滤器("/**","authc")的配置无效,建议自己重新判断
        if ( subject==null || !subject.isAuthenticated() ){
            ResultMap message = new ResultMap(2,"请先登录",null);
            //这是一个自己写的方法用于封装response
            Tool.responseWrite(JSON.toJSONString(message),response);
            return false;
        }
        String[] roles = (String[]) mappedValue;
        if ( roles == null || roles.length == 0 ){
            //没有角色限制通过
            return true;
        }
        for ( String s: roles
               ) {
            if ( subject.hasRole(s) ){
                //只要当前用户包含一个角色 就通过
                return true;
            }
        }
        return false;
    }
    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}

再写一个配置类

@Configuration
public class ShiroConfig {

    @Autowired
    ShiroDao shiroDao;
    /**
     * 创建shiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //设置自定义filter ,key值要与 下方 动态授权的值一致.默认为roles,但是不要使用默认roles,否则会引起动态授权失败
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("oaRoles",new RolesFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        /**
         * shiro 内置过滤器,实现权限相关拦截
         *      常用的过滤器:
         *          anon: 无需认证(登陆) 可以访问
         *          authc: 必须认证才可以访问
         *          user: 如果使用rememberMe 的功能可以直接访问
         *          perms: 该资源必须得到资源权限才可以访问
         *          role: 该资源必须得到角色权限才可以访问
         */
        Map<String,String> authMap = new LinkedHashMap<>();
        /*//shiro 读取 过滤顺序 至上而下
        filterMap.put("/user/login","anon");
        //权限设置
        filterMap.put("/user/info","perms[user:info]");
        filterMap.put("/**","authc");*/
        /**
         * 动态授权,从数据库获取权限
         */
        List<PermissionInfo> perms = shiroDao.selectAll();
        for ( PermissionInfo p:perms
               ) {
            if ( p.getSort()==2 ){
                p.setRole("oaRoles["+p.getRole()+"]");
            }
            authMap.put(p.getUrl(),p.getRole());
        }
        authMap.put("/**","oaRoles");
        //修改跳转的登陆页面,这边的路径不改默认是login.jsp
        shiroFilterFactoryBean.setLoginUrl("/err/toLogin");
        //修改未授权的返回值
        shiroFilterFactoryBean.setUnauthorizedUrl("/err/noAuth");
        //设置权限map
        shiroFilterFactoryBean.setFilterChainDefinitionMap(authMap);
        return shiroFilterFactoryBean;

    }
    /**
     * DefaultWebSecurityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm")MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(myRealm);
        return securityManager;
    }
    /**
     * Realm,需要自定义realm
     */
    @Bean(name = "myRealm")
    public MyRealm getRealm(){
        return new MyRealm();
    }
}

需要注意的是 自定义filter ,key值要与  授权的值一致.默认为roles,但是不要使用默认roles,否则会引起动态授权失败,具体原因,我也不是很清楚,所以希望有人可以解惑...

以及,shiro过滤器的权限是自上而下读取的.所以一般使用/** 路径的过滤条件 建议放在最下面

基本的登陆验证放在你的逻辑层

/**
 * 使用shiro 编写认证操作
 */
//获取subject
Subject subject = SecurityUtils.getSubject();
//封装用户信息
UsernamePasswordToken token = new UsernamePasswordToken(String.valueOf(map.get("account")), MD5.makeMD5(String.valueOf(new StringBuffer(String.valueOf(map.get("password"))).append(KEY))));
//执行登陆方法
try{
    subject.login(token);
    //只要没有异常 登陆成功
    user.setPassword(null);
    Session session1 = subject.getSession();
    session1.setAttribute("user",user);
    return new ResultMap(ServerCode.SUCCESS,"登陆成功",null);
}catch ( UnknownAccountException e ){
    //e.printStackTrace();
    //登陆失败,用户名不存在
    return new ResultMap(ServerCode.FAIL,"用户名不存在",null);
}catch ( IncorrectCredentialsException e ){
    //登陆失败,密码错误
    return new ResultMap(ServerCode.FAIL,"密码错误",null);
}catch ( DisabledAccountException e ){
    //禁用
    return new ResultMap(ServerCode.FAIL,e.getMessage(),null);
}

shiro有很多异常来区分登陆时的各种错误,这个自行了解一下.我这里目前只用了用户名不存在,密码错误,和禁用三种

动态授权部分,我是写了一个初始化权限的操作,然后对权限进行增加,修改,删除后执行这个方法,就可以在不重启服务器的情况就使权限生效

@Override
public Object add(List<PermissionInfo> list) {
    if ( shiroDao.add(list) > 0 ){
        //初始化权限
        updatePermission();
        return new ResultMap(ServerCode.SUCCESS,"添加权限成功",null);
    }
        return new ResultMap(ServerCode.FAIL,"添加权限失败",null);
}

/**
 * 初始化权限
 */
private Map<String,String> load(){
    //从数据获取权限map
    Map<String,String> authMap = new LinkedHashMap<>();
    List<PermissionInfo> perms = shiroDao.selectAll();
    for ( PermissionInfo p:perms
            ) {
        if ( p.getSort()==2 ){
            //使用默认roles,与自定义filter区分,不然动态授权失效,原因未知
            p.setRole("roles["+p.getRole()+"]");
        }
        authMap.putIfAbsent(p.getUrl(),p.getRole());
    }
    return authMap;
}
/**
 * 重新加载权限
 */
public void updatePermission(){

    ShiroFilterFactoryBean shiroFilterFactoryBean = SpringContextHolder.getBean(ShiroFilterFactoryBean.class);
    AbstractShiroFilter filter = null;
        try{
            filter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();
        } catch ( Exception e ) {
            System.out.println(e.getMessage());
            throw new RuntimeException("从shiroFilterFactoryBean中获取filter失败");
        }
        PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) filter.getFilterChainResolver();
        DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
        //清除老的权限控制
        manager.getFilterChains().clear();
        shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
        //初始化权限

        shiroFilterFactoryBean.setFilterChainDefinitionMap(this.load());
        // 重新构建生成
        shiroFilterFactoryBean.getFilterChainDefinitionMap().forEach((k,v) -> manager.createChain(k,v));
        System.out.println("更新成功");
    }

这边值得注意的是.获取权限map的时候.要使用默认的roles ,不能用自定义的filter名,不然会使动态授权失效,原因,不知道,求大佬解惑!!

----------------------------分割线------------

完毕,主要是写一下防止下次写这个的时候忘了,顺便给可能会需要的人一内内帮助.以及求大佬解惑.动态授权失效的原因,感谢阅读

Shiro(二)通过shiro实现登录 连接数据库+集成Springboot

Shiro(二)通过shiro实现登录 连接数据库+集成Springboot

第一步:重写Realm

import com.baizhi.entity.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by mhm on 2019/6/27.
 */
public class MyRealm extends AuthenticatingRealm {
    private Logger logger = LoggerFactory.getLogger(MyRealm.class);

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        //获取到用户输入的用户名
        String username = upToken.getUsername();
        //去数据库中查询,模拟
        logger.info("开始从数据库中查询");
        User user = new User();
        user.setName("xiaobai");
        user.setPassword("1");
//        把查询到的数据封装成AuthenticationInfo对象
        /**
         * 参数1:principal 身份信息 账号
         * 参数2:凭证信息 密码
         * 参数3:realm的名字
         */
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getName(),user.getPassword(),this.getName());
        logger.info("查询结束,返回结果");
        return simpleAuthenticationInfo;
    }
}

 


 

上面这个图,就是说为啥需要重写Realm,

第二步:配置

import com.baizhi.util.MyRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by mhm on 2019/6/27.
 */
//声明,为配置类
@Configuration
public class ShiroConfig {
    private Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
    /**
     * 创建自定义的Realm
     */
    @Bean
    public MyRealm getMyRealm(){
        return new MyRealm();
    }

    /**
     *创建安全管理器
     */
    @Bean//Bean注解修饰的方法的形参,在Spring创建时,从工厂中赋值
    public WebSecurityManager getWebSecurityManager(MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置自定义Realm
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    /**
     * 创建过滤器
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(WebSecurityManager securityManager){
        logger.info("创建Shiro过滤器");
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        /**
         * 设置过滤规则
         */
        logger.info("设置过滤规则");
        Map map = new HashMap();
        /**
         * anon 代表匿名可访问 就是不用登录就可以访问 不过滤
         *
         * authc 代表登录后才能访问  需要认证  会被过滤
         *
         * 支持通配符*
         *
         * 注意拦截规则 一个一个配置
         */
        map.put("/login.jsp","anon");
        map.put("/shiro/*","anon");

        map.put("/main/*", "authc");
        map.put("/guru/*", "authc");
        map.put("/menu/*", "authc");
        filterFactoryBean.setFilterChainDefinitionMap(map);
        /**
         * 配置没有登录,被拦截后的请求路径
         */
        filterFactoryBean.setLoginUrl("userLogin.jsp");
        //设置安全管理器
        logger.info("设置安全管理器");
        filterFactoryBean.setSecurityManager(securityManager);
        return filterFactoryBean;
    }
}

 

 3.在Controller中使用

@RequestMapping("login")
    public @ResponseBody void login(String username,String password){
        logger.info("开始登录");
        //1.把数据封装成Token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        //2.获取Subject
        Subject subject = SecurityUtils.getSubject();
        //3.登录
        try {
            subject.login(usernamePasswordToken);
            logger.info("登录成功");
        } catch (AuthenticationException e) {
           // e.printStackTrace();
            logger.info("登录失败");
        }
    }

我这里就做一个模拟,不实际跳转了。

spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制

spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制

@H_301_0@1.添加maven依赖(先安装好cas-server-3.5.2,安装步骤请查看本文参考文章)

    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.2.4</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.2.4</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-cas</artifactId>
      <version>1.2.4</version>
    </dependency>    
@H_301_0@2.启动累添加@ServletComponentScan注解

@SpringBootApplication
public class Application {
  /**
   * main function
   * @param args params
   */
  public static void main(String[] args){
    ConfigurableApplicationContext context = SpringApplication.run(Application.class,args);
    //test if a xml bean inject into springcontext successful
    User user = (User)context.getBean("user1");
    System.out.println(JSONObject.toJSONString(user));
  }
}
@H_301_0@ 3.配置shiro+cas

package com.hdwang.config.shiroCas;
import com.hdwang.dao.UserDao;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasSubjectFactory;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterfactorybean;
import org.apache.shiro.web.filter.authc.logoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoproxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.DelegatingFilterProxy;
import javax.servlet.Filter;
import javax.servlet.annotation.WebListener;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * Created by hdwang on 2017/6/20.
 * shiro+cas 配置
 */
@Configuration
public class ShiroCasConfiguration {
  private static final Logger logger = LoggerFactory.getLogger(ShiroCasConfiguration.class);
  // cas server地址
  public static final String casServerUrlPrefix = "https://localhost:8443/cas";
  // Cas登录页面地址
  public static final String casLoginUrl = casServerUrlPrefix + "/login";
  // Cas登出页面地址
  public static final String caslogoutUrl = casServerUrlPrefix + "/logout";
  // 当前工程对外提供的服务地址
  public static final String shiroServerUrlPrefix = "http://localhost:8081";
  // casFilter UrlPattern
  public static final String casFilterUrlPattern = "/cas";
  // 登录地址
  public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern;
  // 登出地址
  public static final String logoutUrl = caslogoutUrl+"?service="+shiroServerUrlPrefix;
  // 登录成功地址
  public static final String loginSuccessUrl = "/home";
  // 权限认证失败跳转地址
  public static final String unauthorizedUrl = "/error/403.html";
  @Bean
  public EhCacheManager getEhCacheManager() {
    EhCacheManager em = new EhCacheManager();
    em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
    return em;
  }
  @Bean(name = "myShiroCasRealm")
  public MyShiroCasRealm myShiroCasRealm(EhCacheManager cacheManager) {
    MyShiroCasRealm realm = new MyShiroCasRealm();
    realm.setCacheManager(cacheManager);
    //realm.setCasServerUrlPrefix(ShiroCasConfiguration.casServerUrlPrefix);
    // 客户端回调地址
    //realm.setCasService(ShiroCasConfiguration.shiroServerUrlPrefix + ShiroCasConfiguration.casFilterUrlPattern);
    return realm;
  }
  /**
   * 注册单点登出listener
   * @return
   */
  @Bean
  public ServletListenerRegistrationBean singleSignOutHttpSessionListener(){
    ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
    bean.setListener(new SingleSignOutHttpSessionListener());
//    bean.setName(""); //默认为bean name
    bean.setEnabled(true);
    //bean.setorder(Ordered.HIGHEST_PRECEDENCE); //设置优先级
    return bean;
  }
  /**
   * 注册单点登出filter
   * @return
   */
  @Bean
  public FilterRegistrationBean singleSignOutFilter(){
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setName("singleSignOutFilter");
    bean.setFilter(new SingleSignOutFilter());
    bean.addUrlPatterns("/*");
    bean.setEnabled(true);
    //bean.setorder(Ordered.HIGHEST_PRECEDENCE);
    return bean;
  }
  /**
   * 注册DelegatingFilterProxy(Shiro)
   *
   * @return
   * @author SHANHY
   * @create 2016年1月13日
   */
  @Bean
  public FilterRegistrationBean delegatingFilterProxy() {
    FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
    filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
    // 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
    filterRegistration.addInitParameter("targetFilterLifecycle","true");
    filterRegistration.setEnabled(true);
    filterRegistration.addUrlPatterns("/*");
    return filterRegistration;
  }
  @Bean(name = "lifecycleBeanPostProcessor")
  public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
  }
  @Bean
  public DefaultAdvisorAutoproxyCreator getDefaultAdvisorAutoproxyCreator() {
    DefaultAdvisorAutoproxyCreator daap = new DefaultAdvisorAutoproxyCreator();
    daap.setProxyTargetClass(true);
    return daap;
  }
  @Bean(name = "securityManager")
  public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroCasRealm myShiroCasRealm) {
    DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
    dwsm.setRealm(myShiroCasRealm);
//   <!-- 用户授权/认证信息Cache,采用EhCache 缓存 -->
    dwsm.setCacheManager(getEhCacheManager());
    // 指定 SubjectFactory
    dwsm.setSubjectFactory(new CasSubjectFactory());
    return dwsm;
  }
  @Bean
  public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
    aasa.setSecurityManager(securityManager);
    return aasa;
  }
  /**
   * CAS过滤器
   *
   * @return
   * @author SHANHY
   * @create 2016年1月17日
   */
  @Bean(name = "casFilter")
  public CasFilter getCasFilter() {
    CasFilter casFilter = new CasFilter();
    casFilter.setName("casFilter");
    casFilter.setEnabled(true);
    // 登录失败后跳转的URL,也就是 Shiro 执行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer验证tiket
    casFilter.setFailureUrl(loginUrl);// 我们选择认证失败后再打开登录页面
    return casFilter;
  }
  /**
   * ShiroFilter<br/>
   * 注意这里参数中的 StudentService 和 IscoreDao 只是一个例子,因为我们在这里可以用这样的方式获取到相关访问数据库的对象,
   * 然后读取数据库相关配置,配置到 shiroFilterfactorybean 的访问规则中。实际项目中,请使用自己的Service来处理业务逻辑。
   *
   * @param securityManager
   * @param casFilter
   * @param userDao
   * @return
   * @author SHANHY
   * @create 2016年1月14日
   */
  @Bean(name = "shiroFilter")
  public ShiroFilterfactorybean getShiroFilterfactorybean(DefaultWebSecurityManager securityManager,CasFilter casFilter,UserDao userDao) {
    ShiroFilterfactorybean shiroFilterfactorybean = new ShiroFilterfactorybean();
    // 必须设置 SecurityManager
    shiroFilterfactorybean.setSecurityManager(securityManager);
    // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
    shiroFilterfactorybean.setLoginUrl(loginUrl);
    // 登录成功后要跳转的连接
    shiroFilterfactorybean.setSuccessUrl(loginSuccessUrl);
    shiroFilterfactorybean.setUnauthorizedUrl(unauthorizedUrl);
    // 添加casFilter到shiroFilter中
    Map<String,Filter> filters = new HashMap<>();
    filters.put("casFilter",casFilter);
    // filters.put("logout",logoutFilter());
    shiroFilterfactorybean.setFilters(filters);
    loadShiroFilterChain(shiroFilterfactorybean,userDao);
    return shiroFilterfactorybean;
  }
  /**
   * 加载shiroFilter权限控制规则(从数据库读取然后配置),角色/权限信息由MyShiroCasRealm对象提供doGetAuthorizationInfo实现获取来的
   *
   * @author SHANHY
   * @create 2016年1月14日
   */
  private void loadShiroFilterChain(ShiroFilterfactorybean shiroFilterfactorybean,UserDao userDao){
    /////////////////////// 下面这些规则配置最好配置到配置文件中 ///////////////////////
    Map<String,String> filterChainDeFinitionMap = new LinkedHashMap<String,String>();
    // authc:该过滤器下的页面必须登录后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
    // anon: 可以理解为不拦截
    // user: 登录了就不拦截
    // roles["admin"] 用户拥有admin角色
    // perms["permission1"] 用户拥有permission1权限
    // filter顺序按照定义顺序匹配,匹配到就验证,验证完毕结束。
    // url匹配通配符支持:? * **,分别表示匹配1个,匹配0-n个(不含子路径),匹配下级所有路径
    //1.shiro集成cas后,首先添加该规则
    filterChainDeFinitionMap.put(casFilterUrlPattern,"casFilter");
    //filterChainDeFinitionMap.put("/logout","logout"); //logut请求采用logout filter
    //2.不拦截的请求
    filterChainDeFinitionMap.put("/css/**","anon");
    filterChainDeFinitionMap.put("/js/**","anon");
    filterChainDeFinitionMap.put("/login","anon");
    filterChainDeFinitionMap.put("/logout","anon");
    filterChainDeFinitionMap.put("/error","anon");
    //3.拦截的请求(从本地数据库获取或者从casserver获取(webservice,http等远程方式),看你的角色权限配置在哪里)
    filterChainDeFinitionMap.put("/user","authc"); //需要登录
    filterChainDeFinitionMap.put("/user/add/**","authc,roles[admin]"); //需要登录,且用户角色为admin
    filterChainDeFinitionMap.put("/user/delete/**",perms[\"user:delete\"]"); //需要登录,且用户有权限为user:delete
    //4.登录过的不拦截
    filterChainDeFinitionMap.put("/**","user");
    shiroFilterfactorybean.setFilterChainDeFinitionMap(filterChainDeFinitionMap);
  }
}
package com.hdwang.config.shiroCas;
import javax.annotation.postconstruct;
import com.hdwang.dao.UserDao;
import com.hdwang.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
/**
 * Created by hdwang on 2017/6/20.
 * 安全数据源
 */
public class MyShiroCasRealm extends CasRealm{
  private static final Logger logger = LoggerFactory.getLogger(MyShiroCasRealm.class);
  @Autowired
  private UserDao userDao;
  @postconstruct
  public void initproperty(){
//   setDefaultRoles("ROLE_USER");
    setCasServerUrlPrefix(ShiroCasConfiguration.casServerUrlPrefix);
    // 客户端回调地址
    setCasService(ShiroCasConfiguration.shiroServerUrlPrefix + ShiroCasConfiguration.casFilterUrlPattern);
  }
//  /**
//   * 1、CAS认证,验证用户身份
//   * 2、将用户基本信息设置到会话中(不用了,随时可以获取的)
//   */
//  @Override
//  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
//
//    AuthenticationInfo authc = super.doGetAuthenticationInfo(token);
//
//    String account = (String) authc.getPrincipals().getPrimaryPrincipal();
//
//    User user = userDao.getByName(account);
//    //将用户信息存入session中
//    SecurityUtils.getSubject().getSession().setAttribute("user",user);
//
//    return authc;
//  }
  /**
   * 权限认证,为当前登录的Subject授予角色和权限
   * @see 经测试:本例中该方法的调用时机为需授权资源被访问时
   * @see 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
   * @see 经测试:如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    logger.info("##################执行Shiro权限认证##################");
    //获取当前登录输入的用户名,等价于(String) principalCollection.fromrealm(getName()).iterator().next();
    String loginName = (String)super.getAvailablePrincipal(principalCollection);
    //到数据库查是否有此对象(1.本地查询 2.可以远程查询casserver 3.可以由casserver带过来角色/权限其它信息)
    User user=userDao.getByName(loginName);// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
    if(user!=null){
      //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
      SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
      //给用户添加角色(让shiro去验证)
      Set<String> roleNames = new HashSet<>();
      if(user.getName().equals("boy5")){
        roleNames.add("admin");
      }
      info.setRoles(roleNames);
      if(user.getName().equals("李四")){
        //给用户添加权限(让shiro去验证)
        info.addStringPermission("user:delete");
      }
      // 或者按下面这样添加
      //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
//      simpleAuthorInfo.addRole("admin");
      //添加权限
//      simpleAuthorInfo.addStringPermission("admin:manage");
//      logger.info("已为用户[mike]赋予了[admin]角色和[admin:manage]权限");
      return info;
    }
    // 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址
    return null;
  }
}
package com.hdwang.controller;
import com.hdwang.config.shiroCas.ShiroCasConfiguration;
import com.hdwang.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpSession;
/**
 * Created by hdwang on 2017/6/21.
 * 跳转至cas server去登录(一个入口)
 */
@Controller
@RequestMapping("")
public class CasLoginController {
  /**
   * 一般用不到
   * @param model
   * @return
   */
  @RequestMapping(value="/login",method= RequestMethod.GET)
  public String loginForm(Model model){
    model.addAttribute("user",new User());
//   return "login";
    return "redirect:" + ShiroCasConfiguration.loginUrl;
  }
  @RequestMapping(value = "logout",method = { RequestMethod.GET,RequestMethod.POST })
  public String loginout(HttpSession session)
  {
    return "redirect:"+ShiroCasConfiguration.logoutUrl;
  }
}
package com.hdwang.controller;
import com.alibaba.fastjson.JSONObject;
import com.hdwang.entity.User;
import com.hdwang.service.datajpa.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
 * Created by hdwang on 2017/6/19.
 */
@Controller
@RequestMapping("/home")
public class HomeController {
  @Autowired
  UserService userService;
  @RequestMapping("")
  public String index(HttpSession session,ModelMap map,HttpServletRequest request){
//    User user = (User) session.getAttribute("user");
    System.out.println(request.getUserPrincipal().getName());
    System.out.println(SecurityUtils.getSubject().getPrincipal());
    User loginUser = userService.getLoginUser();
    System.out.println(JSONObject.toJSONString(loginUser));
    map.put("user",loginUser);
    return "home";
  }
}
@H_301_0@ 4.运行验证

@H_301_0@登录

@H_301_0@访问:http://localhost:8081/home

@H_301_0@跳转至:https://localhost:8443/cas/login?service=http://localhost:8081/cas

@H_301_0@输入正确用户名密码登录跳转回:http://localhost:8081/cas?ticket=ST-203-GUheN64mOZec9IWZSH1B-cas01.example.org

@H_301_0@最终跳回:http://localhost:8081/home

@H_301_0@登出

@H_301_0@访问:http://localhost:8081/logout

@H_301_0@跳转至:https://localhost:8443/cas/logout?service=http://localhost:8081

@H_301_0@由于未登录,又执行登录步骤,所以最终返回https://localhost:8443/cas/login?service=http://localhost:8081/cas

@H_301_0@这次登录成功后返回:http://localhost:8081/

@H_301_0@cas server端登出(也行)

@H_301_0@访问:https://localhost:8443/cas/logout

@H_301_0@再访问:http://localhost:8081/home 会跳转至登录页,perfect!

@H_301_0@以上所述是小编给大家介绍的spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持!

您可能感兴趣的文章:

  • spring boot实战教程之shiro session过期时间详解
  • Spring shiro + bootstrap + jquery.validate 实现登录、注册功能
  • SpringBoot+Shiro学习之密码加密和登录失败次数限制示例
  • springboot 在ftl页面上使用shiro标签的实例代码

今天关于SpringBoot集成Shiro 实现动态加载权限springboot动态加载配置的讲解已经结束,谢谢您的阅读,如果想了解更多关于Apollo — 与springboot集成实现动态刷新、shiro 整合 springboot 实现动态授权、Shiro(二)通过shiro实现登录 连接数据库+集成Springboot、spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制的相关知识,请在本站搜索。

本文标签: