springboot快速整合shiro安全框架
*步骤其实不多耐心看*
步骤顺序
- 引入依赖
- 配置shiroConfig.java文件
- 配置自定义realm文件
- 开发 对应的Controller层接口
1、引入依赖
<!--shiro-spring-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!--shiro整合redis-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>
2、创建shiro配置文件 ShiroConfig.java文件 ShiroConfig文件代码如下(直接复制稍作修改即可):
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.kuang.config.shiro.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//ShrioFilterFactoryBean 过滤bena
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
/* 添加shiro的内置过滤器
anon 无需认证就可以访问
authc 必须认证才能访问
user 必须拥有记住我功能 才能用
perms 拥有对某个用户资源才能访问
role 拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
//游客角色
filterMap.put("/tourist/**", "anon");
//授权拦截 符合角色
filterMap.put("/authc/system/**", "roles[root]");
filterMap.put("/authc/user/**", "roles[admin]");
filterMap.put("/authc/images/**", "roles[admin]");
filterMap.put("/authc/operation/**", "roles[admin]");
//认证拦截
filterMap.put("/authc/**", "authc");
// 很多 开发人员 习惯 在最后面添加这行代码 确保 没有考虑到的接口需要登录 但是 注意整合swagger时 需要过滤掉swagger相关的接口
// filterMap.put("/**", "authc");
bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/tourist/noLogin"); //没有登录
bean.setUnauthorizedUrl("/tourist/noAuth"); //没有权限
return bean;
}
// 安全对象 FafaulWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
securityManager.setSessionManager(sessionManager()); // 安全管理器中 设置 sessionManager
securityManager.setCacheManager(cacheManager());
return securityManager;
}
//创建 Realm对象 ,需要自定义
@Bean
public UserRealm userRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher) {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return userRealm;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置散列算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列次数
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
//整合ShiroDialect:用来整合shiro 和 thymeleaf
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
//修改shiro的cookie的名称
public Cookie cookieDAO() {
Cookie cookie = new org.apache.shiro.web.servlet.SimpleCookie();
cookie.setName("WEBSID");
cookie.setMaxAge(3600 * 24 * 2);// 设置为2天
return cookie;
}
//自定义SessionManager
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//将修改的cookie放入sessionManager中
sessionManager.setSessionIdCookie(cookieDAO());
//session超时时间,默认30分钟,会话会超时 单位毫秒
sessionManager.setGlobalSessionTimeout(172800000); //两天
sessionManager.setSessionDAO(redisSessionDAO()); //配置session持久化到redis中
return sessionManager;
}
/**
* 配置redisManager
*/
public RedisManager getRedisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost("106.***.***.***:6379");
redisManager.setPassword("ar******");
return redisManager;
}
/**
* 配置具体cache实现类
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(getRedisManager());
//设置redis过期时间,单位是秒
redisCacheManager.setExpire(3600 * 24 * 2);
return redisCacheManager;
}
/**
* 自定义session持久化
*
* @return
*/
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO1 = new RedisSessionDAO();
redisSessionDAO1.setRedisManager(getRedisManager());
redisSessionDAO1.setExpire(3600 * 24 * 2);
return redisSessionDAO1;
}
/**
* 管理shiro一些bean的生命周期 即bean初始化 与销毁
*
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest , 但是 一般使用配置文件的方式)
*
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new
AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,需
* 要在LifecycleBeanPostProcessor创建后才可以创建
*
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new
DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}
3、自定义Realm(UserRealm.java文件)
import com.kuang.pojo.Permission;
import com.kuang.pojo.Role;
import com.kuang.pojo.User;
import com.kuang.service.UserService;
import com.kuang.utils.CookieUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
//自定义UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
User user = (User) principalCollection.getPrimaryPrincipal(); //拿到用户 如果认证返回的SimpleAuthenticationInfo对象 参数一 传入的是 user对象那这里取到的就是对象 如果是用户名 这里取到的就是用户名
/********查询出 当前用户的所有详细信息 包括用户的角色 和 权限***************/
User userallInfo = userService.findAllUserInfoByUsername(user.getUsername());
List<String> stringRoleList = new ArrayList<>();
List<String> stringPermissionList = new ArrayList<>();
//去不去重 无所谓
List<Role> roleList = userallInfo.getRoleList();
for (Role role : roleList) {
stringRoleList.add(role.getName());
List<Permission> permissionList = role.getPermissionList();
for (Permission p : permissionList) {
stringPermissionList.add(p.getName());
}
}
/***********************************************************************/
//将用户的角色 和 用户的权限 集合传进去 别返回SimpleAuthorizationInfo对象
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(stringRoleList);
simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
return simpleAuthorizationInfo;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
String username = (String) authenticationToken.getPrincipal();
/*****************获取 当前登录的用户名 对应的用户信息*******************/
User user = userService.findAllUserInfoByUsername(username);
if(user == null){
return null;
}
/***********************************************************************/
Subject subject1 = SecurityUtils.getSubject();
Session session = subject1.getSession();
session.setAttribute("loginUser" , user);
// 将用户的密码和用户对象写进去返回,切记整合redis 必须要传对象
return new SimpleAuthenticationInfo(user, user.getPassword(),"");
}
}
4、开发 对应的Controller层接口
对应 接口 图中 已有注释!!! 根据需要 自行修改
5、认证用户登录信息接口
(忽略 swagger注解)
import com.kuang.pojo.User;
import com.kuang.utils.CookieUtils;
import com.kuang.utils.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* 认证模块
*/
@RestController
@Api(tags = "认证模块")
public class TouristController {
/**
* 登录验证
*
* @param username
* @param password
* @return
*/
@PostMapping("/tourist/login")
@ApiOperation(value = "登录效验")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", required = true, dataType = "string", paramType = "query"),
@ApiImplicitParam(name = "password", value = "密码", required = true, dataType = "string", paramType = "query")
})
public Result login(String username, String password,
HttpServletRequest request, HttpServletResponse response) {
if (username.equals("") || password.equals("")) {
return Result.ok().build(500, "用户名密码不能为空");
}
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
try {
Map<String, Object> info = new HashMap<>();
subject.login(usernamePasswordToken);
info.put("WEBSID认证令牌", subject.getSession().getId());
info.put("WEBSID令牌有效期", "48小时");
return Result.ok().build(200, "登录成功", info);
} catch (IncorrectCredentialsException ice) { // 密码不存在
return Result.ok().build(500, "用户名密码不匹配");
} catch (AuthenticationException e) {
return Result.ok().build(500, "用户名不存在,请先注册");
} catch (Exception e) {
return Result.ok().build(500, "登录异常,请稍后重试");
}
}
@PostMapping("/getLoginUserId")
@ApiOperation(value = "获取已登录用户的ID")
public Result getSessionUserId() {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getSession().getAttribute("loginUser");
Map map = new HashMap();
map.put("userId", user.getId());
return Result.ok(map);
}
/**
* 用户未登录提示接口
*
* @return
*/
@GetMapping("/tourist/noLogin")
@ApiOperation(value = "登录信息过期接口(自动无需调用接口)")
public Result noLogin() {
return Result.ok().build(403, "用户登录信息已过期,请重新登录");
}
/**
* 用户权限不够提示接口
*
* @return
*/
@GetMapping("/tourist/noAuth")
@ApiOperation(value = "用户权限不足接口(自动无需调用接口)")
public Result noAuth() {
return Result.ok().build(403, "您的用户权限不足,请升级权限");
}
/**
* 退出登录
*
* @param request
* @param response
* @return
*/
@GetMapping("/tourist/logout")
@ApiOperation(value = "退出当前用户登录接口")
public Result logout(HttpServletRequest request, HttpServletResponse response) {
CookieUtils.deleteCookie(request, response, "WEBSID");
return Result.ok("已退出用户登录");
}
}
5、查询出来的用户详细信息json数据 和 用户的基本数据信息json数据,其他参考数据
(1)、查询到的用户基本数据信息,json结构
(2)、查询到的用户详细数据信息,json结构 (表结构 为 多表关系)
(3)、shiro 基本数据库接口 (mysql)
(4)、springboot项目 yml文件配置
spring:
application:
#指定应用的名称建议使用小写
name: zytrade-sevice-mobile
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://106.***.***.***:****/数据库?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
username: test_mybatis
password: ******
driver-class-name: com.mysql.cj.jdbc.Driver
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 30000
#validationQuery: select 'x'
testWhileIdle: false
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
logging:
level:
com.kuang.mapper: debug
server:
port: 8081