其基本功能点如下图所示
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 提供了一些常见的加密组件用于如密码加密 / 解密的。
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.1
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-web
org.apache.tomcat.embed
tomcat-embed-jasper
jstl
jstl
1.2
org.apache.shiro
shiro-spring-boot-starter
1.6.0
org.apache.shiro
shiro-ehcache
1.5.2
org.springframework.boot
spring-boot-starter-data-redis
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
配置类需要三个类,一般来说,可以直接配置自己要拦截那些请求
ShiroFilterFactoryBean
过滤拦截,用于来拦截一些请求。一般会放行公共资源
hiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
System.out.println("进行拦截");
bean.setSecurityManager(securityManager);
Map 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;
}
//管理器
@Bean
public DefaultWebSecurityManager securityManager(@PathVariable Realm realm){
DefaultWebSecurityManager bean = new DefaultWebSecurityManager();
bean.setRealm(realm);
return bean;
}
//realm
@Bean
public Realm realm(){
return userRealm;
}
}
@Mapper
public interface UserMapper {
int save(User user);
User getUser(Map map);
// 根据名字查询角色信息
User findRoleByName(String username);
List findPresById(int id);
}
@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 map) {
return userMapper.getUser(map);
}
@Override
public User findRoleByName(String username) {
return userMapper.findRoleByName(username);
}
@Override
public List findPresById(int id) {
return userMapper.findPresById(id);
}
}
在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 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;
}
}
@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 roles = roleByName.getRoles();
if (!CollectionUtils.isEmpty(roles)){
// 用SimpleAuthorizationInfo来授予权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
roles.forEach(role -> {
// 添加角色
info.addRole(role.getName());
// 用字符串来表示权限,例如:user:*:* 拥有什么样的操作权限
List 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” %>
Md5Hash
更改配置文件的Realm
@Bean
public Realm realm(){
UserRealm userRealm = new UserRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置加密方式
credentialsMatcher.setHashAlgorithmName("MD5");
return userRealm;
}
MD5
加密 Md5Hash md5Hash = new Md5Hash(user.getPassWord());
user.setPassWord(md5Hash.toHex());
user.setSalt(salt);
@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;
}
CacheManager
public class RedisCacheManager implements CacheManager {
@Override
public Cache getCache(String cacheName) throws CacheException {
// cacheName 的值就是在realm里面设置的授权名或者验证名
return new RedisCache<>(cacheName);
}
}
类中方法和reids的操作基本相同。
这里我用的是缓存一个hash数据
public class RedisCache
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 keys() {
return getRedisTemple().opsForHash().keys(this.cacheName);
}
@Override
public Collection 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的一个总结,如果有什么不足之处,还希望各位小伙伴谅解。大家一起深造