shiro和redis集成,前后端分离
前言
框架:springboot+shiro+redis+vue
最近写前后端分离授权的对账平台系统,采取了shiro框架,若采用shiro默认的cookie进行授权验证时,一直存在由于跨域造成前端请求到的cookie每次都不相同,从而无法完成授权及验证的操作,即每次登陆成功时还是会显示未登陆。
Pom的引入
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.1</version>
</dependency>
代码的编写
域
public class CustomRealm extends AuthorizingRealm {
@Resource
private AdminDao adminDao;
@Resource
private PermissionDao permissionDao;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取登录认证成功的主体
Admin admin = (Admin)principals.getPrimaryPrincipal();
// HashMap<String,String> admin = (HashMap)principals.getPrimaryPrincipal();
//根据角色Id查询权限
List<String> perms = new ArrayList<>();
// if (!admin.get("roleId").equals("-1")){
// perms = permissionDao.findPermsByRoles(admin.get("roleId"));
// }
if (!admin.getRoleId().equals("-1")){
perms = permissionDao.findPermsByRoles(admin.getRoleId());
}
//对集合中的字符串做过滤处理,去除 空字符串及null
perms = perms.stream().filter(s->s != null && !s.isEmpty()).collect(Collectors.toList());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(perms);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取通过subject提交的主体信息
String userName = (String)token.getPrincipal();
Admin admin = adminDao.queryByUserName(userName);
if(admin == null||admin.getFlag()==0){
throw new UnknownAccountException("账号不存在或账号不可用,请联系管理员");
}
// HashMap<String,String> map=new HashMap();
// map.put("username",admin.getUsername());
// map.put("roleId",admin.getRoleId());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(admin,admin.getPassword(),ByteSource.Util.bytes(admin.getSalt()),super.getName());
return info;
}
}
shiroConfig
@Configuration
public class ShiroConfig {
@Value("${redis.port}")
private Integer port;
@Value("${redis.host}")
private String host;
/**
* @author zhuyang
* @description 创建域,包括认证管理器,安全管理器
* @date 2021-03-07 15:55
* @param credentialsMatcher: MD5加密器
* @return com.yxkj.web.accountchecking.realm.CustomRealm
*/
@Bean
public CustomRealm customRealm(HashedCredentialsMatcher credentialsMatcher){
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(credentialsMatcher);
return customRealm;
}
/**
* @author zhuyang
* @description 安全管理器 shiro里面所有的权限控制,认证都事先通过他,安全管理器是和过滤器工厂打交道的桥梁
* @date 2021-03-07 15:54
* @param customRealm:
* @return org.apache.shiro.web.mgt.DefaultWebSecurityManager
*/
//只把记住我和域注入到安全管理器
//如果记住我注入到域中,那么安全管理器还要和域打交道,这样就绕了
@Bean
public DefaultWebSecurityManager securityManager(CustomRealm customRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm);
//添加开始
CarWashModularRealmAuthorizer authorizer = new CarWashModularRealmAuthorizer();
List<Realm> list=new ArrayList<>();
list.add(customRealm);
authorizer.setRealms(list);
securityManager.setAuthorizer(authorizer);
//将自定义的会话管理器注入到安全管理器中
securityManager.setSessionManager(sessionManger());
//将自定义的redis缓存管理器注册到安全管理器中
securityManager.setCacheManager(cacheManager());
//添加结束
return securityManager;
}
/**
* @author zhuyang
* @date 2021-03-07 15:57
* @param securityManager: 先经过过滤器工厂,再通过subject提交token给DefaultWebSecurityManager(安全管理器)
* @return org.apache.shiro.spring.web.ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
//修改logout后的地址
filtersMap.put("corsAuthenticationFilter", corsAuthenticationFilter());
LogoutFilter logout = new LogoutFilter();
logout.setRedirectUrl("/unauth");
filtersMap.put("logout",logout);
filterFactoryBean.setSecurityManager(securityManager);
filterFactoryBean.setUnauthorizedUrl("/UnauthorizedUrl");
Map map = new LinkedHashMap();
map.put("/admin/getVerifyCode","anon");
map.put("/admin/subLogin","anon");//访问登录
map.put("/swagger-ui.html","anon");
map.put("/admin/logout","logout");
map.put("/doc.html","anon");
map.put("/webjars/**","anon");
map.put("/swagger-resources","anon");
map.put("/freeQuery/manualCheck","anon");
map.put("/v2/**","anon");
// map.put("/**","anon");
map.put("/**","authc");
filterFactoryBean.setFilters(filtersMap);
filterFactoryBean.setFilterChainDefinitionMap(map);
filterFactoryBean.setLoginUrl("/unauth");
return filterFactoryBean;
}
@Bean
public HashedCredentialsMatcher credentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
/**
* @author zhuyang
* @description 如果您在使用shiro注解配置的同时,引入了spring aop的starter,
* 会有一个奇怪的问题,导致shiro注解的请求,不能被映射,需加入以下配置
* @date 2021-03-07 16:14
* @return org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
/**
* setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
* 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,导致返回404。
* 加入这项配置能解决这个bug
*/
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* @author zhuyang
* @description 开启shiro注解的支持
* @date 2021-03-07 16:20
* @param securityManager:
* @return org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
public CORSAuthenticationFilter corsAuthenticationFilter(){
return new CORSAuthenticationFilter();
}
// @Bean
// public SimpleCookie simpleCookie(){
// SimpleCookie cookie = new SimpleCookie();
// cookie.setName("rememberMe");
// cookie.setMaxAge(18);
// return cookie;
// }
/**
* @author zhuyang
* @description 1.redis的控制器,操作redis
* @date 2021-03-07 14:41
*/
public RedisManager redisManager(){
RedisManager redisManager=new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(2000);
return redisManager;
}
/**
* @author zhuyang
* @description sessiionDao
* @date 2021-03-06 17:31
*/
public RedisSessionDAO redisSessionDAO(){
RedisSessionDAO sessionDAO=new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
// sessionDAO.setExpire(2000);
return sessionDAO;
}
/**
* @author zhuyang
* @description 会话管理器
* @date 2021-03-06 17:30
*/
public DefaultWebSessionManager sessionManger(){
CustomSessionManager sessionManager=new CustomSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setSessionIdCookieEnabled(false);
sessionManager.setSessionIdUrlRewritingEnabled(false);
// sessionManager.setTimeout(new DefaultSessionKey(),100);
return sessionManager;
}
/**
* @author zhuyang
* @description 缓存管理器
* @date 2021-03-06 17:31
*/
public RedisCacheManager cacheManager(){
RedisCacheManager redisCacheManager=new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
}
SessionId的获取
public class CustomSessionManager extends DefaultWebSessionManager {
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader("token");
//如果请求头中有 token 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
//sessionId存放的位置
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
//sesssionId的值
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
//是否验证
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
解决403的问题
public class CORSAuthenticationFilter extends FormAuthenticationFilter {
public CORSAuthenticationFilter() {
super();
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if(request instanceof HttpServletRequest){
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")){
System.out.println("OPTIONS请求");
return true;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}
}
授予管理员的一切权限,且不可修改
public class CarWashModularRealmAuthorizer extends ModularRealmAuthorizer {
@Override
public boolean isPermitted(PrincipalCollection principals, String permission){
// HashMap user = (HashMap) principals.getPrimaryPrincipal();
Admin user = (Admin) principals.getPrimaryPrincipal();
// 如果是管理员拥有所有的访问权限
return user.getUsername().equals("gly") || super.isPermitted(principals, permission);
}
@Override
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
Admin user = (Admin) principals.getPrimaryPrincipal();
// 如果是管理员拥有所有的角色权限
return user.getUsername().equals("gly") || super.hasRole(principals, roleIdentifier);
}
}
异常拦截
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(AuthorizationException.class)
@ResponseBody
public Map handleAuthorizationException(AuthorizationException e){
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", "1000002");
map.put("msg", "该用户没有对应的权限");
return map;
}
@ExceptionHandler(Exception.class)
@ResponseBody
public Result exceptionHandler(Exception e){
e.printStackTrace();
log.error("异常日志打印"+e.getMessage());
System.out.println(e.getClass().getName());
return new Result(500,e.getMessage());
}
}
其他处理
@Controller
@ApiIgnore
public class ShiroController {
@RequestMapping(value = "/unauth")
@ResponseBody
public Object unauth() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", "1000000");
map.put("msg", "未登录");
return map;
}
@RequestMapping(value = "/UnauthorizedUrl")
@ResponseBody
public Object UnauthorizedUrl() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", "1000001");
map.put("msg", "未授权");
return map;
}
}
XFS