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

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()查看加密后的密码。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容