SpringBoot整合安全中间件Shiro
技术栈:SpringBoot+Shiro
代码实现
-
pom文件加坐标
Springboot版本选择2.7.14 ;java版本1.8 ; shiro做了版本锁定 1.3.2
<properties> <java.version>1.8</java.version> <!--shiro版本锁定--> <shiro.version>1.3.2</shiro.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.16</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> <!--mp--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <!-- SECURITY begin --> <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> <!-- SECURITY end --> </dependencies>
-
主配置文件
#配置数据源 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/spring?serverTimezone=GMT username: root password: 123456 #配置自动驼峰映射 mybatis: configuration: map-underscore-to-camel-case: true type-aliases-package: com.dong.pojo #MP配置自动驼峰映射 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #mybatis所执行的sql输出控制台
-
POJO实体类
Permission
@NoArgsConstructor @AllArgsConstructor @Data @Component @TableName(value = "pe_permission") public class Permission { @TableField(value = "id") private String id; @TableField(value = "name") private String name; @TableField(value = "code") private String code; @TableField(value = "description") private String description; }
Role
@NoArgsConstructor @AllArgsConstructor @Data @TableName(value = "pe_role") @Component public class Role { @TableField(value = "id") private String id; @TableField(value = "name") private String name; @TableField(value = "code") private String code; @TableField(value = "description") private String description; // 外部属性 @TableField(exist = false) private List<Permission> permissions; }
Users
@NoArgsConstructor @AllArgsConstructor @Data @TableName(value = "pe_user") @Component public class Users { @TableId(value = "id") 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 List<Role> rolesList; public Users(String id, String username, String password) { this.id = id; this.username = username; this.password = password; } }
-
dao层
UsersMapper
@Mapper public interface UserMapper extends BaseMapper<Users> { @Insert("insert into pe_user(id,username,password,salt) values(#{id},#{username},#{password},#{salt})") public int save(Users users); // 级联查询 @Results(id = "users",value = { @Result(column = "id",property = "rolesList",many = @Many(select = "com.dong.springboot_mp_shiro.com.dong.mapper.RoleMapper.findById")) }) @Select("select * from pe_user where username=#{v}") public Users findUserDetail(String name); // 简单查询 @Select("select * from pe_user where username=#{v}") public Users findBaseUser(String name); }
RoleMapper
@Mapper public interface RoleMapper extends BaseMapper<Role> { @Results(id = "role",value = { @Result(column = "id",property = "permissions",many=@Many(select = "com.dong.springboot_mp_shiro.com.dong.mapper.PermissionMapper.findByPermissionId")) }) @Select("select * from pe_role where id in (select role_id from pe_user_role where user_id =#{v} )") public Role findById(String id); }
PermissionMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.dong.springboot_mp_shiro.com.dong.pojo.Permission; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface PermissionMapper extends BaseMapper<Permission> { @Select("select * from pe_permission where id in (select permission_id from pe_role_permission where role_id =#{v})") public Permission findByPermissionId(String permissionId); }
-
service层
接口:
IUserService
public interface IUserService { public int save(Users users); public Users baseFindUser(String name); public Users findUserDetail(String Name); }
IRoleService
public interface IRoleService { }
IPermissionService
public interface IPermissionService { }
实现类:
UserServiceImp
@Service public class UserServiceImp implements IUserService { @Autowired(required = false) private UserMapper uMapper; @Override public int save(Users users) { System.out.println("service:"+ users); // 获取salt字符串 String salt = DigestsUtil.generateSalt(); // 密码加密 String password = DigestsUtil.generatePassword(users.getPassword(), salt); users.setPassword(password); users.setSalt(salt); int res = uMapper.save(users); return res; } @Override public Users baseFindUser(String name) { Users baseUser = uMapper.findBaseUser(name); return baseUser; } @Override public Users findUserDetail(String name) { Users userDetail = uMapper.findUserDetail(name); return userDetail; } }
-
controller层
@RestController public class UserController { @Autowired(required = false) private UserServiceImp service; // 首页 @RequiresPermissions("user-home") @RequestMapping("/user/home") public String home(){ return "访问个人主页成功"; } // 用户注册 @RequiresPermissions("user-add") @RequestMapping("/user/{id}") public String save(@PathVariable String id){ /*int res = service.save(users); if(res>0){ return "添加成功"; }else{ return "添加失败"; }*/ return "新增成功"; } @RequiresPermissions("user-delete") @RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE) public String delete(@PathVariable String id){ return "删除成功"; } @RequiresPermissions("user-update") @RequestMapping(value = "/user/{id}",method = RequestMethod.PUT) public String update(@PathVariable String id){ return "修改成功"; } @RequiresPermissions("user-find") @RequestMapping(value = "/user",method = RequestMethod.GET) public String find(){ return "查询成功"; } // 登录认证 @RequestMapping("/login") public String login(Users users){ try { // 构造登录令牌 UsernamePasswordToken token = new UsernamePasswordToken(users.getUsername(), users.getPassword()); // 获取subject Subject subject = SecurityUtils.getSubject(); // 调用subject认证 subject.login(token); return "登录成功"; } catch (AuthenticationException e) { return "用户名或密码错误"; } } // 未登录跳转 @RequestMapping("/autherror") public String autherror(){ return "未认证,请登录"; } }
==@RequiresPermissions(" "):==标注访问该资源需要的权限
-
执行subject.long登录方法,执行Reaml的AuthenticationException方法
-
鉴权授权,执行Reaml的AuthorizationInfo方法
-
-
MyRealm
public class MyRealm extends AuthorizingRealm { @Autowired(required = false) private UserServiceImp serviceImp; // 授权鉴权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 获取已经认证的用户数据 Users users = (Users) principalCollection.getPrimaryPrincipal(); // 查询用户的详细信息 Users userDetail = serviceImp.findUserDetail(users.getUsername()); HashSet<String> perms = new HashSet<>(); // 权限set集合 HashSet<String> roles = new HashSet<>(); // 角色set集合 for(Role role: userDetail.getRolesList() ){ roles.add(role.getCode()); 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 { // 获取用户登陆输入的密码(token) UsernamePasswordToken upToken = (UsernamePasswordToken)authenticationToken; // 用户输入的账号 String username = upToken.getUsername(); Users users = serviceImp.baseFindUser(username); if(users != null){ SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(users, users.getPassword(), ByteSource.Util.bytes(users.getSalt()), "MyRealm"); return info; } // 账号查不到,返回null(抛出异常) return null; } @PostConstruct // 属性初始化 public void initCredentialsMatcher(){ // 指定密码算法 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(DigestsUtil.SHA1); // 指定迭代次数 hashedCredentialsMatcher.setHashIterations(DigestsUtil.COUNTS); // 生成密码比较器 setCredentialsMatcher(hashedCredentialsMatcher); } }
@PostConstruct注解,属性初始化
加密工具类
public class DigestsUtil { // 编码方式 public static final String SHA1="SHA-1"; // 加密次数 public static final Integer COUNTS=369; // 获取salt字符串 public static String generateSalt(){ SecureRandomNumberGenerator secureRandomNumberGenerator = new SecureRandomNumberGenerator(); return secureRandomNumberGenerator.nextBytes().toHex(); } // 生成密文密码 public static String generatePassword(String input,String salt){ return new SimpleHash(SHA1,input,salt,COUNTS).toString(); } }
-
Shiro配置类: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(); //new 自定义的Reaml } /** * 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 SecurityManager 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 filterFactoryBean = new ShiroFilterFactoryBean(); // 2.设置安全管理器 filterFactoryBean.setSecurityManager(defaultWebSecurityManager()); // 3.通用配置(跳转登录页面,为授权跳转的页面) filterFactoryBean.setLoginUrl("/autherror"); //4.设置过滤器集合 //key = 拦截的url地址 //value = 过滤器类型 LinkedHashMap<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/login","anon");//当前请求地址可以匿名访问 filterMap.put("/user/**","authc");// 当前请求地址必须认证之后才可以访问 // 在过滤器工程内设置系统过滤器 filterFactoryBean.setFilterChainDefinitionMap(filterMap); return filterFactoryBean; } }
-
统一异常处理器
@ControllerAdvice public class UserControllerAdv { @ExceptionHandler(value = AuthorizationException.class) @ResponseBody public String tongyi(HttpServletRequest request , HttpServletResponse response, AuthorizationException e){ return "未授权---统一异常处理器"; } }
Shiro实现分布式会话SessionManager
代码结构:代码结构和SpringBoot整合Shiro中的案例相同
问题:如图
解决思路:将当前的session会话存到缓存Redis中
实现步骤:
- 创建RedisSessionDao extends AbstractSessionDAO
- 配置ShiroConfig
代码实现:
-
RedisSessionDao
public class RedisSessionDao extends AbstractSessionDAO { @Autowired private RedisTemplate redisTemplate; //创建会话 @Override protected Serializable doCreate(Session session) { Serializable sessionId = generateSessionId(session); assignSessionId(session, sessionId); redisTemplate.opsForValue().set(sessionId,session); return sessionId; } @Override protected Session doReadSession(Serializable sessionId) { return (Session) redisTemplate.opsForValue().get(sessionId); } @Override public void delete(Session session) { redisTemplate.delete(session.getId()); } @Override public Collection<Session> getActiveSessions() { return Collections.emptySet(); } @Override public void update(Session session) { redisTemplate.opsForValue().set(session.getId(),session); } }
-
需要操作Redis,整合Redis
-
导入redis坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
yaml主配置文件配置redis
server: port: 8080 #配置数据源 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/spring?serverTimezone=GMT username: root password: 123456 redis: port: 6379 #配置自动驼峰映射 mybatis: configuration: map-underscore-to-camel-case: true type-aliases-package: com.dong.pojo #MP配置自动驼峰映射 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #mybatis所执行的sql输出控制台
-
ShiroConfig
向容器中注入一个SessionDao,把SessionDao绑定给会话管理器
@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(); } // 向容器中注入一个SessionDao @Bean public SessionDAO redisSessionDao(){ RedisSessionDao sessionDAO = new RedisSessionDao(); return sessionDAO; } /** * 3.创建会话管理器 */ @Bean public DefaultWebSessionManager sessionManager(){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); //把SessionDao绑定给会话管理器 sessionManager.setSessionDAO(redisSessionDao()); sessionManager.setSessionValidationSchedulerEnabled(false); sessionManager.setSessionIdCookieEnabled(true); sessionManager.setSessionIdCookie(sessionIdCookie()); sessionManager.setGlobalSessionTimeout(3600000); return sessionManager; } //4.创建安全管理器 @Bean public SecurityManager 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 filterFactoryBean = new ShiroFilterFactoryBean(); // 2.设置安全管理器 filterFactoryBean.setSecurityManager(defaultWebSecurityManager()); // 3.通用配置(跳转登录页面,为授权跳转的页面) filterFactoryBean.setLoginUrl("/autherror"); //4.设置过滤器集合 //key = 拦截的url地址 //value = 过滤器类型 LinkedHashMap<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/login","anon");//当前请求地址可以匿名访问 filterMap.put("/user/**","authc"); filterFactoryBean.setFilterChainDefinitionMap(filterMap); return filterFactoryBean; } }
-