秋雨 De Blog

一个技术小白的个人博客

spring-boot项目整合shiro权限框架与redis

介绍:

Apache Shiro™ 是一个功能强大且易于使用的 Java 安全框架,可执行身份验证、授权、加密和会话管理。通过Shiro易于理解的API,您可以快速、轻松地保护任何应用程序的安全–从最小的移动应用程序到最大的网络和企业应用程序。

https://shiro.apache.org

shiro由subject,SecurityManager,realm三部分组成。

Subject本质上是当前正在执行的用户的特定安全 “视图”。而 “用户 “这个词通常意味着一个人,Subject可以是一个人,但它也可以代表第三方服务、守护进程账户、cron作业或任何类似的东西–基本上是任何当前正在与软件交互的东西。
SecurityManager是Shiro架构的核心,它作为一种 “保护伞 “对象,协调其内部的安全组件,这些组件共同构成一个对象图。然而,一旦为应用程序配置了SecurityManager及其内部对象图,通常就不会再管它了,应用程序开发人员几乎把所有的时间都花在了Subject API上。
Realms作为Shiro和您的应用程序的安全数据之间的 “桥梁 “或 “连接器”。当需要与安全相关数据(如用户帐户)进行实际交互以执行身份验证(登录)和授权(访问控制)时,Shiro将从为应用程序配置的一个或多个Realms中查找其中的许多内容。
在这个意义上,Realm本质上是一个特定于安全的DAO:它封装了数据源的连接详情,并根据需要向Shiro提供相关数据。配置Shiro时,您必须指定至少一个Realm用于验证和/或授权。SecurityManager 可以配置多个 Realms,但至少需要一个 Realms。
Shiro提供了开箱即用的Realms,用于连接到许多安全数据源(又称目录),如LDAP、关系数据库(JDBC)、文本配置源(如INI和属性文件)等等。如果默认的Realms不能满足您的需求,您可以插入自己的Realm实现来表示自定义数据源。
与其他内部组件一样,Shiro SecurityManager管理如何使用Realms来获取安全和身份数据,以表示为Subject实例。

首先需要添加pom.xml依赖

 <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.3.2</version>
        </dependency>
       <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.2.3</version>
        </dependency>

因为shiro没有整合至spring-boot框架里,所以需要我们自己创建配置文件,新建ShroConfig类

shiro类

shiroFilter访问控制配
filterChainDefinitionMap.put(“/login”,”anon”);

/*
权限详解
        anon:例子/admins/**=anon 没有参数,表示可以匿名使用。

        authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数

        roles(角色):例子/admins/user/**=roles[admin],参数可以写多个,
        多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/
        user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles
        ()方法。

        perms(权限):例子/admins/user/**=perms[user:add:*],参数可以写多个,
        多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["use
        r:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPerm
        itedAll()方法。

        rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。

        port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转
        到schemal://serverName:8081?queryString,其中schmal是协议http或https等,
        serverName是你访问的host,8081是url配置里port的端口,queryString
         */
//配置示例
@Bean
public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager ){
    ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    Map filterChainDefinitionMap=new LinkedHashMap();
    filterChainDefinitionMap.put("/login","anon");
    filterChainDefinitionMap.put("/static/**","anon");
    filterChainDefinitionMap.put("/**","authc");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    Map<String,Filter> filterMap=new LinkedHashMap<>();
    shiroFilterFactoryBean.setFilters(filterMap);
    return shiroFilterFactoryBean;
}

securityManagers realm域配置 redis配置

//realm单域配置
@Bean
public org.apache.shiro.mgt.SecurityManager securityManagers(){
    DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
    //将realm加进去
    defaultWebSecurityManager.setRealm(myRealms());
    //redisCache配置
    defaultWebSecurityManager.setCacheManager(redisCacheManager());
    //redisSession配置
    defaultWebSecurityManager.setSessionManager(sessionManager());
    return defaultWebSecurityManager;
}

接下来需要配置redis,rediscache,redissession将他们组合起来

@Bean//首先是realm
public MyRealm myRealms(){
    MyRealm myRealms= new MyRealm();
    //可以在这里增加MD5密码加密功能
    myRealms.setCredentialsMatcher(credentialsMatcher());
    return myRealms;
}

//然后是redis管理配置
@Bean
    public RedisManager redisManager(){
        RedisManager redisManager=new RedisManager();
        redisManager.setHost("127.0.0.1:6379");
        return redisManager;
    }
//加密配置
    @Bean
    public HashedCredentialsMatcher credentialsMatcher(){
        HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
        //确定当前验证密码为MD5验证
        matcher.setHashAlgorithmName("MD5");
         //在这里可以指定哈希次数
        matcher.setHashIterations(3)
        return matcher;
    }
    //redis cache配置
    @Bean
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager=new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    //redis sessionDAO配置
    @Bean
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO=new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    //redis session配置
    @Bean
    public DefaultWebSessionManager sessionManager(){
        DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }
    //我们还可以使用shiro的注解权限控制只需要增加两个配置函数
    //开启shiro 注解
    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor advisor(){
        AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager());
        return advisor;
    }
//这样我们就可以在controller层的函数上增加注释来控制权限
//例:
          //@RequiresRoles(value = {"user","admin"},logical = Logical.OR(AND))指定两个可以使用该接口的角色可以是同时拥用两个角色也可以拥有其中一个
    @ResponseBody
    @RequiresRoles(value = {"admin"})
    @RequestMapping(value = "get",method = RequestMethod.POST)
    public Object get(){
        return null;
    }

这里shiro就算配置完成 接下来我们需要写realm,写realm前提要写好DAO层,服务器层与controller层,其中shiro登录认证与权限需要一个可以通过登录账号获取用户信息服务通过用户信息可以获取用户角色与权限的服务,服务写完后我们开始写realm类将其继承与AuthorizingRealm类

public class Realm extends AuthorizingRealm {
    /**
     * Retrieves the AuthorizationInfo for the given principals from the underlying data store.  When returning
     * an instance from this method, you might want to consider using an instance of
     * {@link SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases.
     *
     * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
     * @return the AuthorizationInfo associated with this principals.
     * @see SimpleAuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //权限认证函数
        return null;
    }

    /**
     * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
     * authentication token.
     * <p/>
     * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
     * more and letting Shiro do the rest.  But in some systems, this method could actually perform EIS specific
     * log-in logic in addition to just retrieving data - it is up to the Realm implementation.
     * <p/>
     * A {@code null} return value means that no account could be associated with the specified token.
     *
     * @param token the authentication token containing the user's principal and credentials.
     * @return an {@link AuthenticationInfo} object containing account data resulting from the
     * authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
     * @throws AuthenticationException if there is an error acquiring data or performing
     *                                 realm-specific authentication logic for the specified <tt>token</tt>
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //登录认证
        return null;
    }
}
//登录函数细节
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("用户认证");
        //获取controlller subject.login()函数的数据
        //将输入的账号获取道,存到username里
        String username=(String) token.getPrincipal();
        //这里可以获取到密码但是没有必要,框架可以自己对比与输入的密码
        String userpwd=String.valueOf((char[])(token.getCredentials()));
        System.out.println(username);
        System.out.println(userpwd);
        通过输入的账号去数据库把用户实体获取到
        UserBean userBean=userService.FindByName(username);
        if(userBean==null) return null;
        这里是把信息放到shiro框架中
        SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(
                userBean,
                userBean.getUserpwd(),
                getName()
        );
        return authenticationInfo;
    }

认证需要一个服务,可以提供当前用户所有的角色和所有的权限

//权限认证函数细节
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        /**
         * <P>
         *     只在初次认证进行调用,再调用直接调redis
         */
        System.out.println("权限认证");
        SimpleAuthorizationInfo  authenticationInfo= new SimpleAuthorizationInfo();
        //获取当前登录的用户通过数据拿到当前用户的角色与权限然后放到authenticationInfo中,然后return authenticationInfo
        UserBean userBean=(UserBean) principalCollection.getPrimaryPrincipal();
        System.out.println("*********"+userBean);
        try {
            List<AllRole> roles = roleService.ListRoles(userBean.getId());
            authenticationInfo.setStringPermissions(permissionService.listPermission(userBean.getId()));
            for (AllRole role : roles) authenticationInfo.addRole(role.getRoleName());
        }catch (Exception e){
            e.printStackTrace();
        }
        return authenticationInfo;
    }

接下来是controller

@RequestMapping(value = "/loginIn", method = RequestMethod.POST)
    public String login(UserBean userBean) {

        //这里我们需要一个调取subject
        Subject subject= SecurityUtils.getSubject();
       //通过UsernamePasswordToken 的构造函数把用户账号和密码放进去然后通过subject.login登录
        UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(userBean.getUsername(),userBean.getUserpwd());
            subject.login(usernamePasswordToken);
          return "success";
    }

如果密码错误或者没有该用户会报异常,我们可以写全局异常处理类来做这个事情,也可以用try来捕获异常,这里我使用的全局异常类新MyGlobalExceptionHandler类

@ControllerAdvice
//说明当前类是全局异常处理类
public class MyGlobalExceptionHandler  {

    
    @ExceptionHandler(UnknownAccountException.class)
    public ResponseEntity<Object> unknown(UnknownAccountException e){
        return new ResponseEntity<>(new ResultVO(0,"用户不存在"), HttpStatus.NOT_FOUND);
    }

    
    @ExceptionHandler(IncorrectCredentialsException.class)
    public ResponseEntity<Object>  incorrect(IncorrectCredentialsException e){
        return new ResponseEntity<>(new ResultVO(0,"用户名密码错误"), HttpStatus.NOT_FOUND);
    }

    
    @ExceptionHandler(ExpiredCredentialsException.class)
    public ResponseEntity<Object> expired(ExpiredCredentialsException e ){
        return new ResponseEntity<>(new ResultVO(0,"登录过期,请重新登录"), HttpStatus.NOT_FOUND);

    }

  
    @ExceptionHandler({UnauthenticatedException.class, AuthenticationException.class})
    public ResponseEntity<Object> thenticated(){
        return new ResponseEntity<>(new ResultVO(0,"未登录,请先登录"), HttpStatus.NOT_FOUND);
    }

    
    @ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
    public ResponseEntity<Object> authorized(){
        return new ResponseEntity<>(new ResultVO(0,"无权限"),HttpStatus.NOT_FOUND);
    }
}

至此,shiro框架的登录与权限已搭建配置完成.

点赞

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注