目录
3、配置文件application.yml(resources文件夹下)
概述
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份 认证、权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。本篇博客将使用Spring Boot框架集成Shiro,同时结合mybatis框架实现用户的认证以及授权功能。
一、技术栈
主框架:springboot
响应层:springMVC
持久层:mybatis
二、环境搭建
1、数据库设计
(1)pe_user:用户表
(2)pe_user_role:用户角色关系表
(3)pe_role:角色表
(4)pe_role_permission:角色权限关系表
(5)pe_permission:权限表
2、相关坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- mybatis+mp -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- SECURITY begin -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<!-- Shiro版本 -->
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
</dependency>
3、配置文件application.yml(resources文件夹下)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shiro_db?serverTimezone=GMT
username: root
password: 123456
mybatis:
configuration:
map-underscore-to-camel-case: true
type-aliases-type: com.dytx.shiro_demo_05.doamin
4、实体类
User类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "pe_user")
public class User {
@TableId(value = "id")
private String id;
@TableField(value = "username")
private String username;
@TableField(value = "password")
private String password;
@TableField(value = "salt")
private String salt;
private Set<Role> roles;//存储用户所拥有的角色(用户与角色 ==> 多对多)
}
Role类:
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "pe_role")
public class Role {
@TableId(value = "id")
private String id;
@TableField(value = "name")
private String name;
@TableField(value = "code")
private String code;
@TableField(value = "description")
private String description;
private Set<Permission> permissions;//存储角色所拥有的权限(角色与权限 ==> 多对多)
}
Permission类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "pe_permission")
public class Permission {
@TableId(value = "id")
private String id;
@TableField(value = "name")
private String name;
@TableField(value = "code")
private String code;
@TableField(value = "description")
private String description;
}
5、数据访问层(Mapper映射、dao层)
UserMapper接口:
@Mapper
public interface UserMapper extends BaseMapper<User> {
//按照姓名查询
@Select("select * from pe_user where username = #{username}")
User findUserByName(String name);
//级联查询(按照id查询)
@Results({
@Result(column = "id", property = "id"),
@Result(column = "username", property = "username"),
@Result(column = "password", property = "password"),
@Result(column = "salt", property = "salt"),
@Result(column = "id", property = "roles",
many = @Many(select = "com.dytx.shiro_demo_05.dao.RoleMapper.findRoleById"))
})
@Select("select * from pe_user where id = #{id}")
User findUserById(String id);
}
RoleMapper接口:
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
//级联查询
@Results({
@Result(column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "code", property = "code"),
@Result(column = "description", property = "description"),
@Result(column = "id", property = "permissions",
many = @Many(select = "com.dytx.shiro_demo_05.dao.PermissionMapper.findPermissionById"))
})
@Select("select * from pe_role where id in (select role_id from pe_user_role where user_id = #{id})")
Set<Role> findRoleById(String id);
}
PermissionMapper接口:
@Mapper
public interface PermissionMapper extends BaseMapper<Permission> {
//级联查询
@Results({
@Result(column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "code", property = "code"),
@Result(column = "description", property = "description")
})
@Select("select * from pe_permission where id in (select permission_id from pe_role_permission where role_id = #{id})")
Set<Permission> findPermissionById(String id);
}
6、业务层(service层)
UserService类:
@Service
public class UserService {
//自动注入
@Autowired(required = false)
private UserMapper userMapper;
//按照姓名查询
public User findUserByName(String name){
return userMapper.findUserByName(name);
}
//按照id查询
public User findUserDetailById(String id){
return userMapper.findUserById(id);
}
}
7、控制器层(controller层)
UserController类:
@RequiresPermissions() --> 访问此方法必须具备的权限
@RestController
public class UserController {
@Autowired
private UserService service;
//个人主页
@RequestMapping(value = "/user/home")
public String home() {
return "访问个人主页成功";
}
//添加
@RequiresPermissions("user-add")
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String add() {
return "添加用户成功";
}
//查询
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String find() {
return "查询用户成功";
}
//更新
@RequestMapping(value = "/user/{id}",method = RequestMethod.PUT)
public String update(@PathVariable String id) {
return "更新用户成功";
}
//删除
@RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
public String delete(@PathVariable String id) {
return "删除用户成功";
}
//用户登录
@RequestMapping(value = "/login")
public String login(User user){
try {
//1.构造登录令牌
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
//2.获取subject
Subject subject = SecurityUtils.getSubject();
//3.调用subject进行登录
subject.login(token);
return "登陆成功!";
} catch (AuthenticationException e) {
return "用户名或密码错误!";
}
}
//未登陆与未授权页面
@RequestMapping(value = "/autherror")
public String autherror(){
return "未登录!";
}
}
8、Shiro身份认证、授权、鉴权
shiro.ini配置文件:(resources文件夹下)
[main]
definitionRealm=com.dytx.shiro_demo_05.realm.MyRealm
securityManager.realms=$definitionRealm
ShiroConfiguration配置类:
@Configuration
public class ShiroConfiguration {
/**
* 1.创建shiro自带cookie对象
*/
@Bean
public SimpleCookie sessionIdCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
//2.创建realm
@Bean
public MyRealm getRealm(){
return new MyRealm();
}
/**
* 3.创建会话管理器
*/
@Bean
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setGlobalSessionTimeout(3600000);
return sessionManager;
}
//4.创建安全管理器
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(getRealm());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 5.保证实现了Shiro内部lifecycle函数的bean执行
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 6.开启对shiro注解的支持
* AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 7.配合DefaultAdvisorAutoProxyCreator事项注解权限校验
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager());
return authorizationAttributeSourceAdvisor;
}
//8.配置shiro的过滤器工厂再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
@Bean
public ShiroFilterFactoryBean shiroFilter(){
//1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器
filterFactory.setSecurityManager(defaultWebSecurityManager());
//3.通用配置(跳转登录页面,为授权跳转的页面)
filterFactory.setLoginUrl("/autherror");//跳转url地址
//4.设置过滤器集合
//key = 拦截的url地址
//value = 过滤器类型
Map<String,String> filterMap = new LinkedHashMap<>();
//key:请求规则 value:过滤器名称
filterMap.put("/login","anon");//当前请求地址可以匿名访问
filterMap.put("/user/**","authc");//当前请求地址必须认证之后可以访问
//在过滤器工程内设置系统过滤器
filterFactory.setFilterChainDefinitionMap(filterMap);
return filterFactory;
}
}
DigestsUtil类:获取加密后的密码和盐值存入数据库,在认证时,Shiro会自动按照加密算法实现对明文的加密,然后与数据库进行匹配,成功则登录认证成功,否则失败。
public class DigestsUtil {
//加密算法名称
public static final String SHA1 = "SHA-1";
//加密次数
public static final Integer COUNTS =369;
/**
* @Description sha1方法
* @param input 需要散列字符串
* @param salt 盐字符串
* @return
*/
public static String show(String input, String salt) {
return new SimpleHash(SHA1, input, salt,COUNTS).toString();
}
/**
* @Description 随机获得salt字符串
* @return
*/
public static String generateSalt(){
SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
return randomNumberGenerator.nextBytes().toHex();
}
/**
* @Description 生成密码字符密文和salt密文
* @param
* @return
*/
public static Map<String,String> entryptPassword(String passwordPlain) {
Map<String,String> map = new HashMap<>();
String salt = generateSalt();
String password =show(passwordPlain,salt);
map.put("salt", salt);
map.put("password", password);
return map;
}
//将控制台输出的密文和盐值以及张三新增进数据库中的pe_user表中
public static void main(String[] args) {
// String name = "张三";
// String pwd = "123456";
// Map map = entryptPassword(pwd);
// System.out.println(map.toString());
String name = "张三";
String pwd = "123456";
Map map = entryptPassword(pwd);
System.out.println(map.toString());
}
}
MyRealm类:
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService service;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.获取已认证的用户数据
String id = (String) principalCollection.getPrimaryPrincipal();//得到唯一的安全数据
//2.根据用户数据获取用户的权限信息(所有角色,所有权限)
User user = service.findUserDetailById(id);
Set<String> roles = new HashSet<>();//所有角色
Set<String> perms = new HashSet<>();//所有权限
for (Role role : user.getRoles()) {
roles.add(role.getId());
for (Permission permission : role.getPermissions()) {
perms.add(permission.getCode());
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(perms);
info.setRoles(roles);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.获取登录的用户名密码(token)
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();//用户录入的账号
//2.根据用户名查询数据库
//mybatis情景下:user对象中包含ID,name,pwd(匿名)
//JPA情景下:user对象中包含ID,name,pwd(匿名),set<角色>,set<权限>
User user = service.findUserByName(username);
//3.判断用户是否存在或者密码是否一致
if (user != null) {
//4.如果一致返回安全数据
//构造方法:安全数据,密码(匿名),混淆字符串(salt),realm域名
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getId(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), "myRealm");
return info;
}
//5.不一致,返回null(抛出异常)
return null;
}
@PostConstruct
public void initCredentialsMatcher() {
//指定密码算法
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(DigestsUtil.SHA1);
//指定迭代次数
hashedCredentialsMatcher.setHashIterations(DigestsUtil.COUNTS);
//生效密码比较器
setCredentialsMatcher(hashedCredentialsMatcher);
}
}
BaseExceptionHandler类:(自定义异常处理器)
//自定义的公共异常处理器
// 1.声明异常处理器
// 2.对异常统一处理
@ControllerAdvice
public class BaseExceptionHandler {
@ExceptionHandler(value = AuthorizationException.class)
@ResponseBody
public String error(HttpServletRequest request, HttpServletResponse response,AuthorizationException e){
return "未授权-异常处理器实现";
}
}
三、测试结果
利用DigestsUtil类获取用户张三和李四的密文密码、盐值插入到数据库pe_user表中,张三的id为1,李四的id为2。有数据库表关系可知,张三拥有的角色有系统管理员和普通员工,拥有的权限有添加用户、查询用户、更新用户和删除用户;李四拥有的角色有普通员工,拥有的权限仅有用户主页。
1、张三
用户登录:
权限查询(以新增为例):
2、李四
用户登录:
权限查询:(李四没有用户新增的权限,由自定义异常处理器处理异常)