Spring Boot+shiro+redis 安全框架权限认证前后端分离【完整版】总结
程序员文章站
2022-03-04 13:33:27
...
一、导入依赖只导入redis以及shiro
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--使授权注解起作用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>
二、新建ShiroConfig配置类
import cn.suse.edu.wasterecovery.config.properties.HbwsProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
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.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.util.Base64Utils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
/**
* @author [email protected]
* @date 2020/2/3 18:30
*/
@Slf4j
@Configuration
public class ShiroConfig {
@Autowired
private HbwsProperties hbwsProperties;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password:}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.database}")
private int database;
@Bean
public ShiroRealm shiroRealm(){
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
/**
* shiro 中配置 redis 缓存
*
* @return RedisManager
*/
private RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host + ":" + port);
if (StringUtils.isNotBlank(password))
redisManager.setPassword(password);
redisManager.setTimeout(timeout);
redisManager.setDatabase(database);
return redisManager;
}
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
private RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* rememberMe cookie 效果是重开浏览器后无需重新登录
*
* @return SimpleCookie
*/
private SimpleCookie sessionIdCookie() {
// 设置 cookie 名称,对应 login.html 页面的 <input type="checkbox" name="rememberMe"/>
SimpleCookie cookie = new SimpleCookie("rememberMe");
// 设置 cookie 的过期时间,单位为秒,这里为一天
cookie.setMaxAge(86400);
//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
cookie.setHttpOnly(true);
return cookie;
}
/**
* 路径过滤规则
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager")SecurityManager securityManager) {
DefaultWebSecurityManager securityManage = new DefaultWebSecurityManager();
//设置自定义realm.
securityManage.setRealm(shiroRealm());
//设置安全管理器
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 设置 securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 登录的 url
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后跳转的 url
shiroFilterFactoryBean.setSuccessUrl("/");
// 未授权 url shiroFilterFactoryBean.setUnauthorizedUrl("/login");
/**
* 保证顺序使用LinkedHashMap
* 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
* 进行身份认证后才能访问
* authc: 所有url都必须认证通过才可以访问;
* anon: 所有url都都可以匿名访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须授权
* role: 该资源必须得到角色权限才可以访问
*/
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 设置免认证 url
String[] anonUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens("/login,/clien/**", ",");
for (String url : anonUrls) {
filterChainDefinitionMap.put(url, "anon");
}
// 配置退出过滤器,其中具体的退出代码 Shiro已经替我们实现了也可以自己设置
filterChainDefinitionMap.put("/user/logout", "logout");
// 除上以外所有 url都必须认证通过才可以访问,未通过认证自动访问 LoginUrl
filterChainDefinitionMap.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
log.info("---------------shirofactory创建成功");
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(ShiroRealm shiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 配置 SecurityManager,并注入 shiroRealm
securityManager.setRealm(shiroRealm);
/**
* session管理
*/
// 配置 SecurityManager,并注入 shiroRealm
securityManager.setRealm(shiroRealm);
// 配置 shiro session管理器
securityManager.setSessionManager(sessionManager());
// 配置 缓存管理类 cacheManager
securityManager.setCacheManager(cacheManager());
// 配置 rememberMeCookie
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* cookie管理对象
*
* @return CookieRememberMeManager
*/
private CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
cookieRememberMeManager.setCookie(sessionIdCookie());
// rememberMe cookie 加密的**
String encryptKey = "hbws_shiro_key";
byte[] encryptKeyBytes = encryptKey.getBytes(StandardCharsets.UTF_8);
String rememberKey = Base64Utils.encodeToString(Arrays.copyOf(encryptKeyBytes, 16));
cookieRememberMeManager.setCipherKey(Base64.decode(rememberKey));
return cookieRememberMeManager;
}
/**
* session 管理对象
*
* @return DefaultWebSessionManager
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection<SessionListener> listeners = new ArrayList<>();
//配置监听
listeners.add(sessionListener());
// 设置 session超时时间我这里设置了60秒
sessionManager.setGlobalSessionTimeout(60* 1000L);
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 配置会话ID生成器
* @return
*/
@Bean
public SessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}
/**
* 开启Shiro注解模式,可以在Controller中的方法上添加注解
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 配置session监听
* @return
*/
@Bean("sessionListener")
public ShiroSessionListener sessionListener(){
ShiroSessionListener sessionListener = new ShiroSessionListener();
return sessionListener;
}
}
三、新建ShiroSessionListener类
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author 73642
*/
public class ShiroSessionListener implements SessionListener{
private final AtomicInteger sessionCount = new AtomicInteger(0);
@Override
public void onStart(Session session) {
sessionCount.incrementAndGet();
}
@Override
public void onStop(Session session) {
sessionCount.decrementAndGet();
}
@Override
public void onExpiration(Session session) {
sessionCount.decrementAndGet();
}
/**
* 获取在线人数使用
* @return
*/
public AtomicInteger getSessionCount() {
return sessionCount;
}
}
四、新建ShiroHelper
import cn.suse.edu.wasterecovery.config.annotation.Helper;
import org.apache.shiro.authz.AuthorizationInfo;
/**
* @author [email protected]
* @date 2020/2/3 18:04
*/
@Helper
public class ShiroHelper extends ShiroRealm {
/**
* 获取当前用户的角色和权限集合
*
* @return AuthorizationInfo
*/
public AuthorizationInfo getCurrentuserAuthorizationInfo() {
return super.doGetAuthorizationInfo(null);
}
}
五、自定义realm,在我们自定义realm中实现了AuthorizingRealm接口,将其方法进行重写,将各种权限对用户进行授权,同时对用户身份进行验证,代码如下,每一行代码具体含义十分详细了。
import cn.suse.edu.wasterecovery.dto.MenuListDto;
import cn.suse.edu.wasterecovery.dto.UserDto;
import cn.suse.edu.wasterecovery.entity.Role;
import cn.suse.edu.wasterecovery.service.UserService;
import cn.suse.edu.wasterecovery.service.shiro.MenuService;
import cn.suse.edu.wasterecovery.service.shiro.RoleService;
import cn.suse.edu.wasterecovery.utils.BasesUtil;
import cn.suse.edu.wasterecovery.utils.EncryptUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils;
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.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author [email protected]
* @date 2020/2/3 16:48
* 自定义实现 ShiroRealm,包含认证和授权两大模块
*
*/
@Component
@Slf4j
@RestController
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private MenuService menuService;
/**
* 执行授权逻辑
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("---------------- 执行 Shiro 权限获取 ---------------------");
UserDto user = (UserDto) SecurityUtils.getSubject().getPrincipal();
String userName = user.getUsername();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 获取用户角色集
List<Role> roleList = this.roleService.findUserRole(userName);
Set<String> roleSet = roleList.stream().map(Role::getRoleName).collect(Collectors.toSet());
simpleAuthorizationInfo.setRoles(roleSet);
// 获取用户权限集
List<MenuListDto> permissionList = this.menuService.findUserPermissions(userName);
Set<String> permissionSet = permissionList.stream().map(MenuListDto::getPerms).collect(Collectors.toSet());
simpleAuthorizationInfo.setStringPermissions(permissionSet);
log.info("---- 获取到以下权限 ----");
log.info(String.valueOf(permissionSet));
log.info("---------------- Shiro 权限获取成功 ----------------------");
return simpleAuthorizationInfo;
}
/**
* 执行认证逻辑
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
log.info("---------------- 执行 Shiro 凭证认证 ----------------------");
UserDto userDto=null;
// 获取用户输入的用户名和密码
String userName = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
// 通过用户名到数据库查询用户信息
userDto = this.userService.findByName(userName);
if (userDto == null)
throw new UnknownAccountException("用户名错误");
if (!StringUtils.equals(EncryptUtil.SHA1(password), userDto.getPassword()))
throw new IncorrectCredentialsException();
if (UserDto.STATUS_LOCK.equals(userDto.getStatus()))
throw new LockedAccountException();
log.info("---------------- Shiro 凭证认证成功 ----------------------");
return new SimpleAuthenticationInfo(userDto, password, getName());
}
}
六、新建ShiroHelper并继承ShiroRealm,用于认证成功后权限拦截
import cn.suse.edu.wasterecovery.config.annotation.Helper;
import org.apache.shiro.authz.AuthorizationInfo;
/**
* @author [email protected]
* @date 2020/2/3 18:04
*/
@Helper
public class ShiroHelper extends ShiroRealm {
/**
* 获取当前用户的角色和权限集合
*
* @return AuthorizationInfo
*/
public AuthorizationInfo getCurrentuserAuthorizationInfo() {
return super.doGetAuthorizationInfo(null);
}
}
七、controller测试
1、登录
@PostMapping("login")
public String UserLogin( UserLoginVo userVo, HttpServletRequest request){
/**
* 使用shiro编写认证
*/
//获取Subject
Subject userSubject = SecurityUtils.getSubject();
//封装用户数据 userVo.isRememberMe()为true实现保存本地cokie,关闭浏览器再次打开无需再次登录
UsernamePasswordToken token = new UsernamePasswordToken(userVo.getUsername(), userVo.getPassword(), userVo.isRememberMe());
try {
// 执行登录方法
userSubject.login(token);
return Result.toJsonP(userDtos);
} catch (UnknownAccountException e) {
return Result.run_false("用户名不存在");
} catch (LockedAccountException e) {
return Result.run_false("账号已被锁定,请联系管理员!");
} catch (IncorrectCredentialsException e) {
return Result.run_false("密码错误");
}
}
2、权限测试新建controller
@GetMapping("test")
@RequiresPermissions("shiro:test")
public String shiroTest(AddSunMenuVo addSunMenuDto) {
return "需要权限";
}
@GetMapping("test2")
public String shiroTest2(AddSunMenuVo addSunMenuDto) {
return "无需权限";
}
有不明白的希望提出来,才开始写博客,希望越来越好
上一篇: tomcat+nginx+docker前后端分离工程部署
下一篇: 原型模式