美文网首页
一步一步教你用shiro——2配置并自定义realm

一步一步教你用shiro——2配置并自定义realm

作者: 江江的大猪 | 来源:发表于2018-04-30 23:41 被阅读1364次

    shiro中realm的是进行认证和授权的组件,自带了几种实现,比如jdbcRealm和iniRealm,实际项目中肯定都是自己实现realm

    • 首先需要建立用户表,存放用户名、密码、权限信息
    CREATE TABLE IF NOT EXISTS `user` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
      `user_name` varchar(10) NOT NULL DEFAULT '' COMMENT '用户名',
      `password` varchar(50) NOT NULL DEFAULT '' COMMENT '用户密码,用户名为盐,五次md5',
      `roles` varchar(20) NOT NULL DEFAULT '' COMMENT '角色名,逗号分隔',
      `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uniq_idx_role_name` (`user_name`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
    
    • 实现dao增删改查(使用的mybatis代理dao),封一个service实际操作user表
    @Service
    public class UserService {
        //mybatis代理实现的dao
        @Resource
        private UserDao userDao;
    
        //根据用户名获得user对象
        public User queryUserByName(String name) {
            try {
                if (StringUtils.isBlank(name)) {
                    return null;
                }
                return userDao.queryUserByName(name);
            } catch (Exception e) {
                log.error("db error when query user:{}", name, e);
            }
            return null;
        }
        
        //根据用户名获得用户的所有角色
        public Set<String> queryUserRole(String userName) {
            User user = queryUserByName(userName);
            if (user == null) {
                return Collections.emptySet();
            }
            List<String> roleList = StringAssist.splitComma(user.getRoles());
            return Sets.newHashSet(roleList);
        }
    }
    
    • 自定义MyRealm继承AuthorizingRealm,分别实现认证和授权的方法
    • doGetAuthenticationInfo是认证的方法,当用户登陆的时候会调用,例如下面
         @PostMapping("login")
        public String login(String username, String password) {
            try {
                //shiro通过SecurityUtils.getSubject()获得主体,主体可以理解为客户端实例,原理在后面讲
                Subject subject = SecurityUtils.getSubject();
                //已经认证过,也就是该客户端已经登陆过
                if (subject.isAuthenticated()) {
                    return "redirect:/static/html/indexLogin.html";
                }
                //一般都使用UsernamePasswordToken,shiro的token中有Principal和Credentials的概念
                //Principal代表当前客户端要登录的用户,Credentials代表证明该用户身份的凭证
                //UsernamePasswordToken将username作为Principal,password作为Credentials
                UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                //rememberMe功能后面讲
                token.setRememberMe(true);
                subject.login(token);
            } catch (AuthenticationException e) {
                //登录失败则跳转到登录失败页面,可能是用户名或密码错误
                return "redirect:/static/html/loginError.html";
            }
            return "redirect:/static/html/indexLogin.html";
        }
    
    • doGetAuthorizationInfo是授权的方法,在拦截器中进行权限校验的时候会调用
    public class MyRealm extends AuthorizingRealm {
    
        @Resource
        private UserService userService;
    
        //用户的权限信息包含roles角色和permission权限两部分,我这里只使用了角色进行进行权限控制
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            //principals.getPrimaryPrincipal()获得的就是当前用户名
            if (principals == null || StringUtils.isBlank((String) principals.getPrimaryPrincipal())) {
                return null;
            }
            //将用户角色信息传入SimpleAuthorizationInfo
            return new SimpleAuthorizationInfo(userService.queryUserRole((String) principals.getPrimaryPrincipal()));
        }
    
        //token实际就是在login时传入的UsernamePasswordToken
        //getPrincipal()中只执行了getUsername(),getCredentials()只执行了getPassword()
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            if (token == null||StringUtils.isBlank((String) token.getPrincipal())) {
                return null;
            }
            //根据token中的用户名查库,获得user对象
            User user = userService.queryUserByName((String) token.getPrincipal());
            if (user == null) {
                return null;
            }
            //SimpleAuthenticationInfo代表该用户的认证信息,其实就是数据库中的用户名、密码、加密密码使用的盐
            //存在数据库中的密码是对用户真是密码通过md5加盐加密得到的,保证安全,及时数据泄露,也得不到真正的用户密码
            //getName()返回该realm的名字,代表该认证信息的来源是该realm,作用不大,一般都是单realm
            //该方法返回后,上层会对token和SimpleAuthenticationInfo进行比较,首先比较Principal(),然后将token的Credentials
            //进行md5加上SimpleAuthenticationInfo中的盐加密,加密结果和SimpleAuthenticationInfo的Credentials比较
            return new SimpleAuthenticationInfo(
                    user.getUserName(), user.getPassword(), ByteSource.Util.bytes(user.getUserName()), getName());
        }
    
    • 在securityManager中注入realm,其中authenticator必须先于realms注入,这一点非常关键,我之前无论如何都无法授权,debug发现authenticator中的realms为空
        <!--非web环境使用DefaultSecurityManager-->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <!--多realm的授权策略设置,配置为必须满足全部realm才算成功,不在realms前配置的话authenticator中的realms集合为空-->
            <!--securityManager注入realms的时候,会把realm也放一份到authenticator中,所以必须写在realms上面!!!-->
            <property name="authenticator">
                <bean class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
                    <property name="authenticationStrategy">
                        <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/>
                    </property>
                </bean>
            </property>
            <!--如果只有一个realm的话,可以直接注入realm属性,不需要注入realms属性-->
            <!--为了以后的扩展,即使只有一个realm还是注入了realms属性(虽然以后估计也都是单realm)-->
            <property name="realms">
                <list>
                    <bean class="com.qunar.lfz.shiro.MyRealm">
                        <property name="credentialsMatcher">
                            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                                <!--指定加密算法-->
                                <property name="hashAlgorithmName" value="MD5"/>
                                <!--指定对密码连续进行5轮md5加密-->
                                <property name="hashIterations" value="5"/>
                            </bean>
                        </property>
                    </bean>
                </list>
            </property>
        </bean>
    
    • PS:因为我们指定了用户的原密码通过5次md5加盐加密进行校验,这也就要求用户注册的时候存入数据库的密码也是经过5次md5加盐加密的。shiro提供了Md5Hash工具类,通过new Md5Hash("原密码", "盐值", 5).toString()查看加密后的密码。

    相关文章

      网友评论

          本文标题:一步一步教你用shiro——2配置并自定义realm

          本文链接:https://www.haomeiwen.com/subject/tsntrftx.html