一、什么是Shiro
Shiro是Apache提供的安全框架,是一个权限管理框架,执行身份验证、授权、密码和会话管理,功能十分强大并且容易使用;
Spring中有Spring Security,是一个权限框架,它和Spring依赖过于紧密,没有Shiro使用简单;
二、核心组成
Subject
主体,即为”当前操作用户“,系统需要对“当前操作用户”进行认证、授权;它仅仅意味着当前跟软件交互的东西;
SecurityManager
安全管理器,主体进行认证和授权都是通过SecurityManager进行,SecurityManager是一个集合,真正做事的不是SecurityManager而是它里面的东西;相当于Spring MVC中的前端控制器;
Realm
数据源,通过realm存取认证、授权相关数据(原来是通过数据库取的);
authenticator认证器和authorizer授权器是调用realm中存储授权和认证的数据和逻辑;
三、SpringBoot整合
来源:https://blog.csdn.net/chy_18883701161/article/details/107454059
前期准备:
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.41</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4.1</version>
</dependency>
mysql建表
-- 用户信息表--
create table user_info(
id bigint primary key,
user_name varchar(50),
pwd varchar(300)
) engine = InnoDB default charset = utf8;
-- 角色信息表--
create table role_info(
id bigint primary key,
role_desc varchar(50)
) engine = InnoDB default charset = utf8;
-- 权限信息表--
create table module_info(
id bigint primary key,
module_code varchar(50),
module_name varchar(50),
parent_module varchar(50)
) engine = InnoDB default charset = utf8;
-- 用户与角色的中间表
create table user_role(
id bigint primary key,
user_id bigint,
role_id bigint
) engine = InnoDB default charset = utf8;
-- 角色与权限的中间表
create table role_module(
id bigint primary key,
role_id bigint,
module_id bigint
) engine = InnoDB default charset = utf8;
自定义Realm
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lq.mycreate.bean.ModuleInfo;
import com.lq.mycreate.bean.RoleInfo;
import com.lq.mycreate.bean.RoleModule;
import com.lq.mycreate.bean.UserInfo;
import com.lq.mycreate.bean.UserRole;
import com.lq.mycreate.mapper.ModuleInfoMapper;
import com.lq.mycreate.mapper.RoleInfoMapper;
import com.lq.mycreate.mapper.RoleModuleMapper;
import com.lq.mycreate.mapper.UserInfoMapper;
import com.lq.mycreate.mapper.UserRoleMapper;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 自定义realm
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserInfoMapper userInfoMapper;
@Autowired
private RoleInfoMapper roleInfoMapper;
@Autowired
private ModuleInfoMapper moduleInfoMapper;
@Autowired
private UserRoleMapper userRoleMapper;
@Autowired
private RoleModuleMapper roleModuleMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权操作");
//获取主体标识
String username = (String) principalCollection.getPrimaryPrincipal();
//根据主体标识查询用户对应的角色、权限
//角色
QueryWrapper<UserInfo> userInfoWrapper = new QueryWrapper<>();
userInfoWrapper.eq("user_name", username);
UserInfo userInfo = userInfoMapper.selectOne(userInfoWrapper);
if (userInfo == null) {
return null;
}
QueryWrapper<UserRole> userRoleWrapper = new QueryWrapper<>();
userRoleWrapper.eq("user_id", userInfo.getId());
List<UserRole> userRoles = userRoleMapper.selectList(userRoleWrapper);
if (CollectionUtils.isEmpty(userRoles)) {
return null;
}
List<RoleInfo> roleInfos = new ArrayList<>();
Set<String> roles = userRoles.stream().map(userRole -> {
RoleInfo roleInfo = roleInfoMapper.selectById(userRole.getRoleId());
if (roleInfo != null) {
roleInfos.add(roleInfo);
return roleInfo.getRoleDesc();
}
return null;
}).filter(node -> node != null).collect(Collectors.toSet());
Set<String> permissions = new HashSet<>();
roleInfos.stream().forEach(role -> {
QueryWrapper<RoleModule> roleModuleWrapper = new QueryWrapper<>();
roleModuleWrapper.eq("role_id", role.getId());
List<RoleModule> roleModules = roleModuleMapper.selectList(roleModuleWrapper);
List<String> collect = roleModules.stream().map(roleModule -> {
ModuleInfo moduleInfo = moduleInfoMapper.selectById(roleModule.getModuleId());
return moduleInfo.getModuleCode();
}).collect(Collectors.toList());
permissions.addAll(collect);
});
//设置并返回主体的授权信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证操作");
//获取主体标识
String username = (String) token.getPrincipal();
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.eq("user_name", username);
UserInfo userInfo = userInfoMapper.selectOne(wrapper);
if (userInfo == null) {
return null;
}
String pwd = userInfo.getPwd();
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, pwd,
ByteSource.Util.bytes(userInfo.getUserName()), this.getName());
return authenticationInfo;
}
public static void main(String[] args) {
SimpleHash simpleHash = new SimpleHash("md5", "123456", "wangwu", 2);
String s = simpleHash.toHex();
System.out.println(s);
}
}
配置ShiroConfig
import com.lq.mycreate.filter.CustomRolesOrAuthorizationFilter;
import com.lq.mycreate.realm.UserRealm;
import com.lq.mycreate.session.CustomSessionManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//@Autowired
//private UserRealm userRealm;
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//设置securityManager
factoryBean.setSecurityManager(securityManager);
//某些页面需要登录后才能访问,如果未登录,会交给指定的url处理;
//如果是前后端分离,设置未controller映射的url:如果前后端未分离,设置为也视图页面的url
factoryBean.setLoginUrl("/login");
//登录成功后跳转到的url
//如果前后端分离,则不用设置此项
//factoryBean.setSuccessUrl("/");
//没有权限,未授权会交给指定的url处理,处理方式一般是:先验证登录,再验证是否有权限;
//如果前后端没有分离,直接设置为视图页面的url
factoryBean.setUnauthorizedUrl("/pub/notPermit.html");
//设置自定义的Filter
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
factoryBean.setFilters(filterMap);
//Filter的执行有一定顺序,应该使用LinkedHashMap,使用HashMap会出问题
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//退出过滤器
filterChainDefinitionMap.put("/logout", "logout");
//不需要登录(校验)就可以访问
filterChainDefinitionMap.put("/pub/**", "anon");
//用户登录后才可以访问
filterChainDefinitionMap.put("/authc/**", "authc");
//角色是管理员的用户才可以访问
//filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin]");
//具有update权限的用户才可以访问
filterChainDefinitionMap.put("/update", "perms[update]");
//[]标识参数是数组,有多个元素时逗号隔开,[video_watch,video_download],必须同时满足[]中的要求才会通过
//LinkedHashMap,Filter的执行顺序和添加顺序一致,一般把/**放到最后
//前面的url未匹配的请求路径,就使用/**来过滤
filterChainDefinitionMap.put("/**", "authc");
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return factoryBean;
}
/**
* 获取redisManager
* @return RedisManager
*/
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
//设置redis服务器的地址,默认就是127.0.0.1:6379
redisManager.setHost("127.0.0.1:6379");
return redisManager;
}
/**
* 获取RedisSessionDAO
* @return RedisSessionDAO
*/
public RedisSessionDAO redisSessionDAO(){
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* 获取自定义的sessionManager
* @return
*/
@Bean
public SessionManager sessionManager(){
CustomSessionManager customSessionManager = new CustomSessionManager();
//设置session超时时间,单位ms,默认30min
customSessionManager.setGlobalSessionTimeout(1800_000);
//配置session持久化
customSessionManager.setSessionDAO(redisSessionDAO());
return customSessionManager;
}
public RedisCacheManager redisCacheManager(){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
//设置授权信息对应的key的过期时间,单位s,用户权限有变化时,除了要更新数据库,还需要更新redis中对应的key
//一周
redisCacheManager.setExpire(24*60*60*7);
return redisCacheManager;
}
/**
* 配置SecurityManager
*
* @return
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//如果前后端没有分离,则不必使用sessionManager,不必设置SessionManager相关配置
//securityManager.setSessionManager(sessionManager());
//使用自定义的CacheManager
//securityManager.setCacheManager(redisCacheManager());
//设置Realm,建议放到最后,否则可能会出问题
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* 返回密码匹配器
*
* @return
*/
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置加密算法
matcher.setHashAlgorithmName("md5");
//设置加密次数
matcher.setHashIterations(2);
//设置密文是16进制编码,false是base64编码
matcher.setStoredCredentialsHexEncoded(true);
return matcher;
}
/**
* 返回自定义的realm
*
* @return
*/
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
//设置密码匹配器
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
//取消缓存
userRealm.setCachingEnabled(false);
return userRealm;
}
}
登录控制
import com.alibaba.fastjson2.JSON;
import com.lq.mycreate.bean.UserInfo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(UserInfo userInfo) {
System.out.println("前端页面的参数是:" + JSON.toJSONString(userInfo));
UsernamePasswordToken token = new UsernamePasswordToken(userInfo.getUserName(), userInfo.getPwd());
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return "index.html";
} catch (Exception ex) {
ex.printStackTrace();
return "login.html";
}
}
@RequestMapping("/update")
public String update() {
return "update";
}
}
自定义Filter(未验证)
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Set;
/**
* 自定义过滤器
*/
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mapperValue) throws Exception {
Subject subject = getSubject(request, response);
//获取当前访问路径所需要的角色集合
String[] roles = (String[]) mapperValue;
//没有角色限制,可以直接访问
if (roles == null || roles.length == 0) {
return true;
}
Set<String> roleSet = CollectionUtils.asSet(roles);
for (String role : roleSet) {
if (subject.hasRole(role)) {
return true;
}
}
return false;
}
}
自定义SessionManager(未验证)
public class CustomSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "token";
public CustomSessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (sessionId != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, true);
return sessionId;
} else {
return super.getSessionId(request, response);
}
}
}
UserInfoMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lq.mycreate.mapper.UserInfoMapper">
<resultMap id="UserRole" type="com.lq.mycreate.bean.UserInfo">
<id property="id" column="uid"/>
<result property="userName" column="userName"/>
<result property="pwd" column="pwd"/>
<!--一对多-->
<collection property="userRoles" ofType="com.lq.mycreate.bean.UserRole">
<!--多中的一对一-->
<association property="roleInfo" javaType="com.lq.mycreate.bean.RoleInfo">
<result property="id" column="rid"/>
<result property="roleDesc" column="roleDesc"/>
</association>
</collection>
</resultMap>
<select id="selectRoleByUserName" resultMap="UserRole">
select u.id uid,u.user_name userName,u.pwd pwd,r.id rid,r.role_desc roleDesc from user_info u,user_role
ur,role_info r
where u.id =ur.user_id and ur.role_id=r.id and user_name = #{userName};
</select>
</mapper>
注意
启动的时候会报错:xxx is not eligible for getting processed by all BeanPostProcessors
处理方法:https://blog.csdn.net/u014163312/article/details/124954945