springboot 整和 shiro + redis 验证header令牌,实现前后端分离认证

    技术2023-08-06  65

    springboot 整和 shiro + redis 验证header令牌,实现前后端分离认证

    一、整个项目结构1.引入pom依赖:(数据源之类的依赖导入自己的就好 ,本文不讲数据源)2.自定义Realm ,创建 UserRealm.java 文件3.shiro配置文件,创建 ShiroConfig.java 文件4.上面代码中 会话管理器中shiro默认使用的是 httpSession 我们使用自定义session会话:创建 CustomSessionManager.java 文件5.测试 二、登录三、权限验证

    一、整个项目结构

    蓝色框里面的是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中。

    可以执行验证。

    Processed: 0.014, SQL: 9