一步一步教你用shiro——2配置并自定义realm
2018-04-30 本文已影响1364人
江江的大猪
- 一步一步教你用shiro——1引入shiro框架
- 一步一步教你用shiro——2配置并自定义realm
- 一步一步教你用shiro——3配置并自定义sessionManager
- 一步一步教你用shiro——4配置并自定义sessionDao
- 一步一步教你用shiro——5配置rememberMe
- 一步一步教你用shiro——6总结和心得
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()查看加密后的密码。