Shiro
- 是一个强大灵活的开源安全框架,提供认证、授权、会话管理以及密码加密等功能。
- 关于shiro的介绍,网上一大堆,但是,shiro整合springboot步骤和套路缺很少。下面是自己学习时候的一个总结
其基本功能点如下图所示
- Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
- Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
- Web Support:Web 支持,可以非常容易的集成到 Web 环境;
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
- Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
- Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
shiro三个顶级概念
Subject,SecurityManager
和Realms
-
Subject
是安全领域里的“当前执行用户“。可以是一个人,或者是第三方服务,守护进程帐户,定时任务等等——可以是基本上任何正与软件交互的事物。Subject
的实例都会(也是必须)绑定一个SecurityManager
,对Subject
的操作会转为Subject与SecurityManager之间的交互。 -
SecurityManager
是Shiro架构的核心,像个“保护伞”一样协调内部组件运作。不过一旦SecurityManager配置完成,它就被放一边去了,通常开发人员只需要使用Subject
。- 简单来说,使用
Shiro
时需要把用SecurityManager
管理器去集成Realms
-
- 简单来说,使用
-
Realms
在Shiro和你的安全数据之间扮演“桥梁”或“连接器”的角色。当需要用到安全数据比如用户帐号来进行认证或授权时,Shiro会从应用配置的一个或多个Realms中来查找。Realms
类需要自己来创建。实现AuthorizingRealm
接口。- 重写两个方法,一个是授权,一个是认证
Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;
SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authorizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所以呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);
SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于如密码加密 / 解密的。
案例
1.导入依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
2.配置文件
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis
username: root
password: rot
mvc:
view:
suffix: .jsp
prefix: /
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.item.pojo
3.写配置类
配置类需要三个类,一般来说,可以直接配置自己要拦截那些请求
-
ShiroFilterFactoryBean
过滤拦截,用于来拦截一些请求。一般会放行公共资源-
hiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); System.out.println("进行拦截"); bean.setSecurityManager(securityManager); Map<String, String> map = new HashMap<>(); map.put("/user/login","anon"); // 公共资源 anon->不用验证 map.put("/user/register","anon"); // 公共资源 anon->不用验证 map.put("/register.jsp","anon"); // 公共资源 anon->不用验证 map.put("/**","authc"); // 受限资源 authc->需要验证 //没有认证自动跳到登录页面 bean.setLoginUrl("/login.jsp");
-
-
DefaultWebSecurityManager
默认的安全管理器- 只需要整合
Realm
- 只需要整合
-
Realm
安全实体数据源,需要自己去定义一个-
public class UserRealm extends AuthorizingRealm { // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { return null; }
@Configuration
public class ShiroConfig {
// 过滤
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@PathVariable(“securityManager”) DefaultSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setFilterChainDefinitionMap(map);
return bean;
}
//管理器
@Beanpublic DefaultWebSecurityManager securityManager(@PathVariable Realm realm){ DefaultWebSecurityManager bean = new DefaultWebSecurityManager(); bean.setRealm(realm); return bean; } //realm @Bean public Realm realm(){ return userRealm; }
}
-
4.写业务
4.1 Dao层
@Mapper
public interface UserMapper {
int save(User user);
User getUser(Map<String,String> map);
// 根据名字查询角色信息
User findRoleByName(String username);
List<Pres> findPresById(int id);
}
4.2 Service
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public void register(User user) {
userMapper.save(user);
}
@Override
public User getUser(Map<String, String> map) {
return userMapper.getUser(map);
}
@Override
public User findRoleByName(String username) {
return userMapper.findRoleByName(username);
}
@Override
public List<Pres> findPresById(int id) {
return userMapper.findPresById(id);
}
}
4.3 Collerction
-
在shiro中,如果要进行登录验证,那么可以在登录之前获取Token,然后直接用shiro的登录去登录
@RequestMapping(“login”)
public String login(String username,String password){
// 认证主体
Subject subject = SecurityUtils.getSubject();
try{
// 获取令牌 new UsernamePasswordToken(username,password)
subject.login(new UsernamePasswordToken(username,password));
return “redirect:/index.jsp”;
}catch (UnknownAccountException e){
System.out.println(“用户名错误”);
}catch (IncorrectCredentialsException e){
System.out.println(“密码错误”);
}
return “/redirect:/login.jsp”;
}
认证
这个时候可以去配置自己写的Realm类。配置登录验证
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取身份信息,也就是用户名
String principal = (String) token.getPrincipal();
// 这里用的是工具类来获取业务层对像 ApplicationContextUtils 自己写的
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
Map<String,String> map = new HashMap<>();
map.put("username",principal);
User user = userService.getUser(map);
if (!ObjectUtils.isEmpty(user)){
// 用SimpleAuthenticationInfo来封装数据,shiro会自动来进行登录验证 this.getName()直接这样写
return new SimpleAuthenticationInfo(user.getUserName(),user.getPassWord(),this.getName());
}
return null;
}
}
ApplicationContextUtils.java
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
//根据bean的名字获取工厂中指定的bean
public static Object getBean(String beanName){
System.out.println(context);
return context.getBean(beanName);
}
}
授权
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("===========授权=====");
// 获取身份->去数据库查询对应的权限和角色
String principal = (String) principalCollection.getPrimaryPrincipal();
UserService userService = (UserService)ApplicationContextUtils.getBean("userService");
// 根据身份信息查询数据
User roleByName = userService.findRoleByName(principal);
// 角色认证
List<Role> roles = roleByName.getRoles();
if (!CollectionUtils.isEmpty(roles)){
// 用SimpleAuthorizationInfo来授予权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
roles.forEach(role -> {
// 添加角色
info.addRole(role.getName());
// 用字符串来表示权限,例如:user:*:* 拥有什么样的操作权限
List<Pres> presById = userService.findPresById(role.getId());
if (!CollectionUtils.isEmpty(presById)){
presById.forEach(pres -> {
// 添加权限
info.addStringPermission(pres.getName());
});
}
});
return info;
}
return null;
}
页面
-
thymeleaf和shiro
<%@page contentType=“text/html; UTF-8” pageEncoding=“UTF-8” isELIgnored=“false” %>
<%@taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>
<!doctype html>Document 操作
退出
加密操作
- shiro里面有一个类可以进行加密
Md5Hash
1.开启加密方式
更改配置文件的Realm
@Bean
public Realm realm(){
UserRealm userRealm = new UserRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置加密方式
credentialsMatcher.setHashAlgorithmName("MD5");
return userRealm;
}
2.在添加信息的时候使用MD5
加密
Md5Hash md5Hash = new Md5Hash(user.getPassWord());
user.setPassWord(md5Hash.toHex());
user.setSalt(salt);
缓存
- 使用redis缓存
1.开启缓存配置(Realm最后成型)
@Bean
public Realm realm(){
UserRealm userRealm = new UserRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置加密方式
credentialsMatcher.setHashAlgorithmName("MD5");
// 设置散列次数
credentialsMatcher.setHashIterations(1024);
userRealm.setCredentialsMatcher(credentialsMatcher);
// 设置开启缓存,用自己的缓存了类
userRealm.setCacheManager(new RedisCacheManager());
// 开启缓存
userRealm.setCachingEnabled(true);
userRealm.setAuthenticationCachingEnabled(true); //开启认证缓存
userRealm.setAuthenticationCacheName("authenticationCache");// 设置认证缓存名字
userRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
userRealm.setAuthorizationCacheName("authorizationCache"); // 设置授权缓存名字
return userRealm;
}
2.写一个缓存映射类,只需要继承CacheManager
public class RedisCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
// cacheName 的值就是在realm里面设置的授权名或者验证名
return new RedisCache<>(cacheName);
}
}
3.配置一个缓存类
-
类中方法和reids的操作基本相同。
-
这里我用的是缓存一个hash数据
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) getRedisTemple().opsForHash().get(this.cacheName,k.toString()); } @Override public V put(K k, V v) throws CacheException { getRedisTemple().opsForHash().put(this.cacheName,k.toString(),v); return null; } @Override public V remove(K k) throws CacheException { return (V) getRedisTemple().opsForHash().delete(this.cacheName); } @Override public void clear() throws CacheException { getRedisTemple().delete(this.cacheName); } @Override public int size() { return getRedisTemple().opsForHash().size(this.cacheName).intValue(); } @Override public Set<K> keys() { return getRedisTemple().opsForHash().keys(this.cacheName); } @Override public Collection<V> values() { return null; }
/// 获取redis的操作了类RedisTemplate
private RedisTemplate getRedisTemple(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean(“redisTemplate”);
// 开启序列化的方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
##以上是自己对学习shiro的一个总结,如果有什么不足之处,还希望各位小伙伴谅解。大家一起深造