文章目录
- 前言
- 一、springBoot+shiro环境准备
- 1.数据库
- 2.ssmp环境搭建
- 3.实体类
- 4.三层搭建
- 5.初始化测试数据
- 二、Shiro过滤器
- 1.Shiro认证过滤器
- 2.Shiro授权过滤器
- 三、springBoot+shiro身份认证
- 1.创建Realm,重写认证方法doGetAuthenticationInfo
- 2.创建shiro配置类
- 3.Postman测试
- 四、springBoot+shiro授权,鉴权
- 1.重写授权方法doGetAuthenticationInfo
- 2.访问程序资源(鉴权)
- 3.全局异常处理器
- 4.Postman测试
前言
整合springBoot+shiro流程:
- 环境准备
- 身份认证
2.1 密码加密
2.2 非法请求控制 - 授权,鉴权
一、springBoot+shiro环境准备
项目目录:
1.数据库
认证框架五表设计:
准备user用户表、user_role用户角色关系表、role角色表、 role_permission角色权限关系表、permission权限表;
user用户表:
user_role用户角色关系表:
role角色表:
role_permission角色权限关系表:
permission权限表:
2.ssmp环境搭建
2.1准备依赖
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--test启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
<scope>runtime</scope>
</dependency>
<!--Mybatisplus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<!--lombok作用于实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--shiro相关坐标-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<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>
2.2 配置Yaml文件
#数据源配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
url: jdbc:mysql://localhost:3306/shiro?serverTimezone=GMT
password: 12345678
#Mybatisplus配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.实体类
User用户类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("pe_user")
public class User implements Serializable {
@TableId(value = "id",type = IdType.NONE)
private String id;
@TableField(value = "username")
private String username;
@TableField(value = "password")
private String password;
@TableField(value = "salt")
private String salt;
@TableField(exist = false)
private Set<Role> roles;
}
role角色类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("pe_role")
public class Role implements Serializable {
private String id;
private String name;
private String code;
private String description;
private Set<Permission> permissions;
}
Permission 权限类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("pe_permission")
public class Permission implements Serializable {
private String id;
private String name;
private String code;
private String escription;
}
4.三层搭建
DAO层: 因为是五表构造,需要处理关联关系,所以在这里使用级联查询的方式实现:
UserMapper :
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("select * from pe_user where username =#{username}")
User findByName(String name);
@Select("select * from pe_user where id =#{id}")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "password",column = "password"),
@Result(property = "salt",column = "salt"),
@Result(property = "roles",column = "id",
many = @Many(select = "com.apesource.springboot_shiro_01.dao.RoleMapper.findRoleById"))
})
User findUserDetailById(int id);
}
RoleMapper :
@Mapper
public interface RoleMapper {
@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.apesource.springboot_shiro_01.dao.PermissionMapper.findPermissionById"))
})
// @Select("select * from 角色表 where 角色id in (select 角色ID from 关系表 where 用户id = ?)")
@Select("select * from pe_role where id in (select role_id from pe_user_role where user_id = #{id})")
public Set<Role> findRoleById(String id);
}
PermissionMapper :
@Mapper
public interface PermissionMapper {
@Select("select * from pe_permission where id in (select permission_id from pe_role_permission where role_id = #{id})")
@Results({
@Result(column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "code",property = "code"),
@Result(column = "description", property = "description")
})
public Set<Permission> findPermissionById(long id);
}
Service层: 因为项目只实现Shrio的用户认证和授权,所以只构造User的业务层就足够了。
UserService :
@Service
public class UserService {
@Autowired(required = false)
UserMapper mapper;
public User findByName(String name){
return mapper.findByName(name);
}
public User findUserDetailById(int id){
return mapper.findUserDetailById(id);
}
}
Controller层:
@RestController
public class ShiroController {
@Autowired
UserService service;
/**
* @RequiresPermissions() -- 访问此方法必须具备的权限
* @RequiresRoles() -- 访问此方法必须具备的角色
*
*/
@RequiresPermissions("user-home")
@RequestMapping(value = "/user/home")
public String home() {
return "访问个人主页成功";
}
//添加
@RequiresPermissions("user-add")
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String add() {
return "添加用户成功";
}
//查询
@RequiresPermissions("user-find")
@RequestMapping(value = "/user/find",method = RequestMethod.GET )
public String find(){
return "查询成功";
}
//更新
@RequiresPermissions("user-update")
@RequestMapping(value = "/user/{id}",method = RequestMethod.PUT)
public String update(@PathVariable String id) {
return "更新用户成功";
}
//删除
@RequiresPermissions("user-delete")
@RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
public String delete(@PathVariable String id) {
return "删除用户成功";
}
//未登陆与未授权页面
@RequestMapping(value="/autherror")
public String autherror() {
return "未登录";
}
//用户登录
@RequestMapping(value="/login")
public String login(User user) {
try {
//1.构造登录令牌
UsernamePasswordToken upToken = new UsernamePasswordToken(user.getUsername(),user.getPassword());
//2.获取subject
Subject subject = SecurityUtils.getSubject();
//3.调用subject进行登录
subject.login(upToken);
return "登录成功";
}catch (Exception e) {
e.printStackTrace();
return "用户名或密码错误";
}
}
}
5.初始化测试数据
因为在实战场景下,数据库的密码是不可以明文保存的,这样对于数据的安全性是巨大的隐患,而shiro也支持数据加密的功能。
加密工具类:
public class DigestUtil {
//算法方式
public static final String SHA1 = "SHA-1";
public static final String SHA256 = "SHA-256";
//加密次数
public static final Integer Counts =369;
/**
* @Description show
* @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",passwordPlain);
map.put("密文密码",password);
return map;
}
由于没有实现注册功能,所以对于数据库的密文密码通过自己操作工具类输入进去:
public static void main(String[] args) {
String name ="张三丰";
String password = "123123";
Map<String, String> map = entryptPassword(password);
System.out.println(map.toString());
}
二、Shiro过滤器
Shiro内置了很多默认的过滤器,比如身份验证、授权等相关的,shiro也是通过过滤器的原理来实现认证和授权的扩展功能的。
1.Shiro认证过滤器
2.Shiro授权过滤器
三、springBoot+shiro身份认证
1.创建Realm,重写认证方法doGetAuthenticationInfo
使用@PostConstruct注解修饰的init方法就会在Spring容器的启动时自动的执行
public class MyRealm extends AuthorizingRealm {
@Autowired
UserService service;
/**
* @Description 自定义密码比较器
* bean标签 init-method属性
*/
@PostConstruct
public void initCredentialsMatcher() {
//指定密码算法
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(DigestUtil.SHA1);
//指定迭代次数
hashedCredentialsMatcher.setHashIterations(DigestUtil.Counts);
//生效密码比较器
setCredentialsMatcher(hashedCredentialsMatcher);
}
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.获取登录的用户名密码(token)
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
String username = token.getUsername();
//2.根据用户名查询数据库
User user = service.findByName(username);
//3.判断用户是否存在或者密码是否一致
if(user==null){
throw new UnknownAccountException("账户不存在");
}
//4.如果一致返回安全数据
//通过SimpleAuthenticationInfo校验数据
//构造方法:安全数据,密码(匿名),混淆字符串(salt),realm域名
return new SimpleAuthenticationInfo(user.getId(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),"MyRealm");
}
2.创建shiro配置类
shiro配置类需要配置八个步骤,比较繁琐,最重要的是shiroFilter过滤器,用于实现非法请求的处理。
@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;
}
}
3.Postman测试
成功:
合法请求资源:
失败:
非法请求资源:
四、springBoot+shiro授权,鉴权
1.重写授权方法doGetAuthenticationInfo
授权方法:
操作的时候,判断用户是否具有响应的权限
一定先认证再授权
先认证 – 安全数据
再授权 – 根据安全数据获取用户具有的所有操作权限
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.获取已认证的用户数据
String id1 = (String) principalCollection.getPrimaryPrincipal();
int i = Integer.parseInt(id1);
User user = service.findUserDetailById(i);
//2.根据用户数据获取用户的权限信息(所有角色,所有权限)
Set<String> roles = new HashSet<>();//所有角色
Set<String> perms = new HashSet<>();//所有权限
for (Role role : user.getRoles()) {
roles.add(role.getName());
for (Permission perm : role.getPermissions()) {
perms.add(perm.getCode());
}
}
//将角色和权限信息返回
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(perms);
info.setRoles(roles);
return info;
}
2.访问程序资源(鉴权)
注解实现:直接在控制器接口上加注解即可;
注解实现权限:如果权限信息不匹配,抛出异常(则使用异常处理器解决);
常用注解:
@RequiresPermissions – 访问此方法必须具备的权限
@RequiresRoles – 访问此方法必须具备的角色
@RequiresAuthentication – 表明当前用户需是经过认证的用户
@ RequiresGuest --表明该用户需为”guest”用户
@ RequiresUser – 当前用户需为已认证用户或已记住用户
具体实现在controller层的每个业务接口,这里只举例一个:
//个人主页
@RequiresPermissions("user-home")
@RequestMapping(value = "/user/home")
public String home() {
return "访问个人主页成功";
}
@RequiresPermissions("user-add")//参数:对应权限的code值
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String add() {
return "添加用户成功";
}
3.全局异常处理器
专门用于处理权限不足的异常信息:
@ControllerAdvice
public class BaseExceptionHandler {
@ExceptionHandler(value = AuthorizationException.class)
@ResponseBody
public String error(HttpServletRequest request, HttpServletResponse response, AuthorizationException e) {
return "未授权-异常处理器实现";
}
}
4.Postman测试
张三丰用户只有user-home权限,没有add权限;
1.直接访问权限方法会被拦截做登录:
2.访问有权限的资源
成功
3.访问没有权限的资源
失败,被全局异常处理器处理。