新手上路:使用Shiro快速搭建一个角色验证框架
前言
相信很多Shiro新手都和我一样,刚上手Shiro时候一脸懵。Realm不是个权限库吗?怎么会做认证和校验的活儿?为什么在Shiro的配置文件里又见到它了?Redis怎么集成到Shiro?Session储存的值怎么交给Redis管理?且看我用一个简单的小demo一一道来。
demo要求
1、角色共有三个:教师、学生、管理员。
2、教师和学生分别使用工号和学号登录,管理员算在教师内。
3、登录时完成身份验证和权限验证。
4、认证信息和权限信息交给Redis存储。
5、Session内存储的信息交给Redis存储。
第一步:设计角色与权限
根据要求,我们需要建两张表:学生表和教师表,在教师表中设计一个字段用来区分教师和管理员。
角色设计思路:工号和学号不会重复,所以可以使用工号和学号在数据库中查询老师和学生。从老师表中查出的赋予老师角色,从学生表中查出的赋予学生角色。
1、教师表:
CREATE TABLE `teacher` (
`t_id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`job_id` varchar(32) NULL DEFAULT '' COMMENT '工号',
`password` varchar(32) NULL COMMENT '密码',
`status` tinyint NULL COMMENT '状态码,0为教师,1为管理员',
PRIMARY KEY (`t_id`)
);
2、学生表:
CREATE TABLE `spring`.`Untitled` (
`s_id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`stu_id` varchar(32) NULL COMMENT '学号',
`password` varchar(32) NULL COMMENT '密码',
PRIMARY KEY (`s_id`)
);
现在开始我们来配置Shiro
第二步:引入依赖、配置Realm
引入Shiro依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.10.0</version>
</dependency>
我们来看一下登录鉴权的流程
也就是说subject在登陆时,底层是到Realm进行认证和授权。那么我们就可以新建一个Realm类,继承AuthorizingRealm类,重写这两个方法,定义我们自己的认证和授权方式。
@Component
public class DefinitionRealm extends AuthorizingRealm {
@Autowired
private StudentService studentService;
@Autowired
private TeacherService teacherService;
/**
* 授权方法
* */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("鉴权发生");
//获得主身份信息
String principal = (String) principalCollection.getPrimaryPrincipal();
//根据用户名获取当前用户的角色信息、权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//获取角色
//如果角色为学生,给角色赋予学生权限
Student studentByStuId = studentService.getStudentByStuId(principal);
if(studentByStuId!=null){
simpleAuthorizationInfo.addRole("student");
}
//如果角色为教师,给角色赋予教师权限
Teacher teacherByJobId = teacherService.getTeacherByJobId(principal);
if(teacherByJobId!=null){
if(teacherByJobId.getStatus()==1){
simpleAuthorizationInfo.addRole("admin");
}else {
simpleAuthorizationInfo.addRole("teacher");
}
}
//返回角色权限
return simpleAuthorizationInfo;
}
/**
* 认证方法
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken){
//获得用户名
String principal = (String) authToken.getPrincipal();
//获取学生身份信息
Student student = studentService.getStudentByStuId(principal);
if(student!=null){
return new SimpleAuthenticationInfo(
authToken.getPrincipal(),
student.getPassword(),
principal
);
}
//获取教师身份信息
Teacher teacher = teacherService.getTeacherByJobId(principal);
if(teacher!=null){
return new SimpleAuthenticationInfo(
authToken.getPrincipal(),
teacher.getPassword(),
principal
);
}
return null;
}
}
如此一来,我们就实现了认证和授权的方法。但是我们还需要将Realm放入SecurityManager中进行管理。
第三步:创建Shiro配置文件,并将realm交给securityManager管理
@Configuration
public class ShiroConfiguration {
@Autowired
private DefinitionRealm realm;
//过滤器,设置访问任何url不需要权限
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
definition.addPathDefinition("/**" , "anon");
return definition;
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置realm
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
}
至此,我们的框架算是简单搭建好了,写个controller测试一下
@Controller
public class testController {
@GetMapping("/login")
@ResponseBody
public boolean login(@RequestParam String username,
@RequestParam String password){
AuthenticationToken token = new UsernamePasswordToken(username , password);
Subject subject = SecurityUtils.getSubject();
subject.login(token);
return subject.hasRole("student");
}
}
测试结果:
登录认证和校验完成后我们现在来配置Redis
第四步:配置Redis
引入依赖:
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
<exclusions> <!--要排除shiro-core,不然会有版本冲突-->
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>
在yml文件中将session类型改为Redis
redis: # session设置redis
session:
store-type: redis
timeout: 30m
然后我们现在来配置redis作为Shiro的缓存
配置Redis的存取方法:
public class RedisCache<K , V> implements Cache<K , V> {
private String cacheName;
public RedisCache(){}
public RedisCache(String cacheName){
this.cacheName = cacheName;
}
@Override
public V get(K k) throws CacheException {
return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
}
@Override
public V put(K k, V v) throws CacheException {
getRedisTemplate().opsForHash().put(this.cacheName , k.toString() , v);
return null;
}
@Override
public V remove(K k) throws CacheException {
return (V) getRedisTemplate().opsForHash().delete(this.cacheName , k.toString());
}
@Override
public void clear() throws CacheException {
getRedisTemplate().delete(this.cacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
@Override
public Set<K> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<V> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
这里是配置一些Redis作为缓存的CRUD方法。
配置RedisCacheManeger:
public class RedisCacheManeger implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
return new RedisCache<K , V>(cacheName);
}
}
在Shiro配置文件中设置缓存管理器
@Configuration
public class ShiroConfiguration {
@Autowired
private DefinitionRealm realm;
//过滤器,设置访问任何url不需要权限
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
definition.addPathDefinition("/**" , "anon");
return definition;
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//设置缓存管理器
realm.setCacheManager(new RedisCacheManeger());
//开启全局缓存
realm.setCachingEnabled(true);
//开启认证缓存,指定缓存名称
realm.setAuthenticationCachingEnabled(true);
realm.setAuthenticationCacheName("authenticationCache");
//开启授权缓存,指定缓存名称
realm.setAuthorizationCachingEnabled(true);
realm.setAuthorizationCacheName("authorizationCache");
//给安全管理器设置realm
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
}
现在我们的session和缓存管理器都已经设置好,现在测试一下运行的情况。
现在我们的认证信息、权限信息、session都完美的存储到了数据库里,至此,小demo算是编写完成。
在这篇博客里,关于Shiro的很多知识我没有提到,比如过滤器的设置、ini文件配置。本篇博客在于教会新手如何快速上手Shiro,如果本篇博客的内容不足以支撑读者完成项目的话,可以搜一些其他的优质文章进行深入学习。