springboot 整和 shiro + redis 验证header令牌,实现前后端分离认证
一、整个项目结构
蓝色框里面的是shiro的主要配置
1.引入pom依赖:(数据源之类的依赖导入自己的就好 ,本文不讲数据源)
<!--shiro整合redis-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>
<!--shiro-spring-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
2.自定义Realm ,创建 UserRealm.java 文件
代码如下(直接复制就好):
import com.ihrm.ihrm_manager.pojo.User;
import com.ihrm.ihrm_manager.servier.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
User user = (User) principalCollection.getPrimaryPrincipal(); //拿到用户 如果认证返回的SimpleAuthenticationInfo对象 参数一 传入的是 user对象那这里取到的就是对象 如果是用户名 这里取到的就是用户名
/********查询出 当前用户的所有详细信息 包括用户的角色和权限 这里只是模拟数据 ***************/
User userallInfo = userService.findAllUserInfoByUsername(user.getUsername());
//将用户的角色 和 所有的权限 都存储到这两个集合中即可,自己调用service层代码
List<String> stringRoleList = new ArrayList<>();
List<String> stringPermissionList = new ArrayList<>();
stringRoleList.add("admin");
stringRoleList.add("root");
stringPermissionList.add("select");
stringPermissionList.add("remove");
stringPermissionList.add("update");
stringPermissionList.add("install");
/***********************************************************************/
//将用户的角色 和 用户的权限 集合传进去 别返回SimpleAuthorizationInfo对象
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(stringRoleList);
simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
return simpleAuthorizationInfo;
}
/**
* 认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@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(),this.getName());
}
}
3.shiro配置文件,创建 ShiroConfig.java 文件
代码如下(直接复制):
import com.ihrm.ihrm_manager.config.shiro.CustomSessionManager;
import com.ihrm.ihrm_manager.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.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 javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* shiro配置类
*/
@Configuration
public class ShiroConfig {
/**
* ShrioFilterFactoryBean 过滤bena
*
* @param defaultWebSecurityManager
* @return
*/
@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("/login", "anon");
//认证拦截
filterMap.put("/Company/**", "authc");
//权限拦截
filterMap.put("/Company/**" , "roles[admin]");
//约定俗称会在后面加一个拦截剩余的路径全部要求登录
filterMap.put("/**", "authc");
bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/tourist/noLogin"); //没有登录
bean.setUnauthorizedUrl("/tourist/noAuth"); //没有权限
//其他资源都需要认证 authc 表示需要认证才能进行访问 user表示配置记住我或认证通过可以访问的地址
return bean;
}
/**
* 安全对象 FafaulWebSecurityManager
*
* @param userRealm
* @return
*/
@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对象 ,需要自定义Realm
*
* @param hashedCredentialsMatcher
* @return
*/
@Bean
public UserRealm userRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher) {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return userRealm;
}
/**
* MD5加密
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
/**
* 会话管理器
*
* @return
*/
@Bean
public DefaultWebSessionManager sessionManager() {
CustomSessionManager sessionManager = new CustomSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setSessionIdCookieEnabled(false);//禁用cookie
sessionManager.setSessionIdUrlRewritingEnabled(false);//禁用url重写 url;jsessionid=id
return sessionManager;
}
/**
* 配置redisManager
*
* @return
*/
public RedisManager getRedisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost("127.0.0.1:6379");
return redisManager;
}
/**
* 配置具体cache实现类
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(getRedisManager());
//设置redis过期时间,单位是秒
redisCacheManager.setExpire(3600);
redisCacheManager.setKeyPrefix("ihrm:shiro:cache:"); //设置权限信息缓存的名称前缀
return redisCacheManager;
}
/**
* 自定义session持久化
*
* @return
*/
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(getRedisManager());
redisSessionDAO.setExpire(3600);
redisSessionDAO.setKeyPrefix("ihrm:shiro:session:"); //设置session缓存的名称前缀
return redisSessionDAO;
}
/**
* 管理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;
}
}
4.上面代码中 会话管理器中shiro默认使用的是 httpSession 我们使用自定义session会话:创建 CustomSessionManager.java 文件
代码如下 直接复制:
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* 自定义session管理器
*/
public class CustomSessionManager extends DefaultWebSessionManager {
/**
* 头信息中具有sessionid
* 请求头:Authorization: sessionid
* 指定sessionId的获取方式
*/
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//获取请求头Authorization中的数据
String id = WebUtils.toHttp(request).getHeader("Authorization");
if (StringUtils.isEmpty(id)) {
//如果没有携带,生成新的sessionId
return super.getSessionId(request, response);
} else {
//请求头信息:bearer sessionid
id = id.replaceAll("Bearer ", "");
//返回sessionId
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
}
}
}
5.测试
自定义会话中已经说明了,用户每次请求的时候(也就是前端访问接口的时候) , 需要在header中 携带 Bearer+空格+令牌。
二、登录
登录后 rides中 会有缓存数据 也就是 用户的sessionID 存储到了redis中。下次验证的时候 直接到 缓存中 验证是否有sessionID 有代表登录成功。
三、权限验证
权限登录成功后 下次 当前用户 在此访问其他接口的是否 涉及到用户权限的 不会再去查数据库 , 用户的角色权限信息都存在了redis中。
可以执行验证。