diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md index 33e2f4aa..e07e2cc9 100644 --- a/sa-token-doc/_sidebar.md +++ b/sa-token-doc/_sidebar.md @@ -115,6 +115,7 @@ - [解决跨域问题](/fun/cors-filter) - [技术选型:SSO 与 OAuth2 对比](/fun/sso-vs-oauth2) + - [从 Shiro、SpringSecurity、JWT 迁移](/fun/auth-framework-function-test) - [issue 提问模板](/fun/issue-template) - [为Sa-Token贡献代码](/fun/git-pr) - [Sa-Token开源大事记](/fun/timeline) diff --git a/sa-token-doc/doc.html b/sa-token-doc/doc.html index f1766711..e64e6fa6 100644 --- a/sa-token-doc/doc.html +++ b/sa-token-doc/doc.html @@ -247,7 +247,7 @@ // tab选项卡 tabs: { persist : true, // 是否在刷新页面时重置选项卡 - sync : true, // 页面上的多个tab是否同步切换 + sync : false, // 页面上的多个tab是否同步切换 theme : 'classic', // 主题:'classic', 'material', false tabComments: true, // 用注释来标注选项卡标题,例如: tabHeadings: true // 用标题+粗体来定制选项卡 diff --git a/sa-token-doc/fun/auth-framework-function-test.md b/sa-token-doc/fun/auth-framework-function-test.md new file mode 100644 index 00000000..3aa9a700 --- /dev/null +++ b/sa-token-doc/fun/auth-framework-function-test.md @@ -0,0 +1,1107 @@ +# Java 权限认证框架功能 测试 / 对比 / 迁移。 + +对比以下框架的常见功能,为项目技术栈迁移提供代码示例 + +- Sa-Token +- Apache Shiro +- Spring Security +- JWT + + +> [!TIP| label:注意事项] +> - 因个人精力&能力有限,本篇只展示部分常见功能的对比,也欢迎大家一起贡献案例,提交pr。 +> - 代码案例仓库:[https://gitee.com/sa-tokens/auth-framework-function-test](https://gitee.com/sa-tokens/auth-framework-function-test) +> - 注:本篇主要展示一些常见功能不同框架的实现差异,而非每个框架的所含功能点对比。 + + +--- + + + +### 登录 & 注销 & 查询会话状态 + + + + +测试 Controller +``` java +@RestController +@RequestMapping("/acc/") +public class LoginController { + + @Autowired + SysUserDao sysUserDao; + + // 测试登录 ---- http://localhost:8081/acc/doLogin?username=zhang&password=123456 + @RequestMapping("doLogin") + public AjaxJson doLogin(String username, String password) { + // 校验 + SysUser user = sysUserDao.findByUsername(username); + if(user == null) { + return AjaxJson.getError("用户不存在"); + } + if(!user.getPassword().equals(password)) { + return AjaxJson.getError("密码错误"); + } + // 登录 + StpUtil.login(user.getId()); + StpUtil.getSession().set("user", user); + return AjaxJson.getSuccess("登录成功"); + } + + // 查询登录状态 ---- http://localhost:8081/acc/isLogin + @RequestMapping("isLogin") + public AjaxJson isLogin() { + if(StpUtil.isLogin()) { + return AjaxJson.getSuccess("已登录,账号id:" + StpUtil.getLoginId()); + } + return AjaxJson.getError("未登录"); + } + + // 测试注销 ---- http://localhost:8081/acc/logout + @RequestMapping("logout") + public AjaxJson logout() { + StpUtil.logout(); + return AjaxJson.getSuccess("注销成功"); + } + +} +``` + + +自定义 Realm +``` java +public class MyRealm extends AuthorizingRealm { + + @Autowired + private SysUserDao sysUserDao; + + // 加载用户信息 + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { + String username = (String)token.getPrincipal(); + SysUser sysUser = sysUserDao.findByUsername(username); + if(sysUser == null){ + return null; + } + return new SimpleAuthenticationInfo( + sysUser, + sysUser.getPassword(), + getName() + ); + } + + // 加载权限信息 + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + return null; + } + +} +``` + +Shiro 配置类 +``` java +@Configuration +public class ShiroConfigure { + + @Bean + public MyRealm myRealm() { + return new MyRealm(); + } + + @Bean + public DefaultWebSecurityManager securityManager() { + DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); + manager.setRealm(myRealm()); + return manager; + } + + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean() { + ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); + bean.setSecurityManager(securityManager()); + return bean; + } + +} +``` + +测试 Controller +``` java +@RestController +@RequestMapping("/acc/") +public class LoginController { + + // 测试登录 ---- http://localhost:8082/acc/doLogin?username=zhang&password=123456 + @RequestMapping("doLogin") + public AjaxJson doLogin(String username, String password) { + Subject subject = SecurityUtils.getSubject(); + try { + subject.login(new UsernamePasswordToken(username, password)); + return AjaxJson.getSuccess("登录成功!"); + } catch (AuthenticationException e) { + e.printStackTrace(); + return AjaxJson.getError(e.getMessage()); + } + } + + // 查询登录状态 ---- http://localhost:8082/acc/isLogin + @RequestMapping("isLogin") + public AjaxJson isLogin() { + Subject subject = SecurityUtils.getSubject(); + if(subject.isAuthenticated()) { + SysUser sysUser = (SysUser)subject.getPrincipal(); + return AjaxJson.getSuccess("已登录,账号id:" + sysUser.getId()); + } + return AjaxJson.getError("未登录"); + } + + // 测试注销 ---- http://localhost:8082/acc/logout + @RequestMapping("logout") + public AjaxJson logout() { + SecurityUtils.getSubject().logout(); + return AjaxJson.getSuccess("注销成功"); + } + +} +``` + + + + + +### 账号密码登录(加盐 MD5) + + + + +测试 Controller +``` java +@RequestMapping("doLogin") +public AjaxJson doLogin(String username, String password) { + // 校验 + SysUser user = sysUserDao.findByUsername(username); + if(user == null) { + return AjaxJson.getError("用户不存在"); + } + String salt = "abc"; + if(!user.getPassword().equals(SaSecureUtil.md5(salt + password))) { + return AjaxJson.getError("密码错误"); + } + // 登录 + StpUtil.login(user.getId()); + StpUtil.getSession().set("user", user); + return AjaxJson.getSuccess("登录成功"); +} +``` + + +自定义 Realm Bean 设定密码凭证器 +``` java +@Bean +public MyRealm myRealm() { + MyRealm realm = new MyRealm(); + // 设定凭证匹配器 + HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); + credentialsMatcher.setHashAlgorithmName("md5"); + realm.setCredentialsMatcher(credentialsMatcher); + // 返回 + return realm; +} +``` + +自定义 Realm 实现类 doGetAuthenticationInfo 方法返回 slat 信息 +``` java +// 加载用户信息 +@Override +protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { + String username = (String)token.getPrincipal(); + SysUser sysUser = sysUserDao.findByUsername(username); + if(sysUser == null){ + return null; + } + + return new SimpleAuthenticationInfo( + sysUser, + sysUser.getPassword(), + ByteSource.Util.bytes("abc"), // 指定 slat 信息 + getName() + ); +} +``` + +登录代码照旧 + + + + + +### 从上下文获取当前登录 User 信息 + + +``` java +// 从上下文获取当前登录 User 信息 +@RequestMapping("getCurrUser") +public AjaxJson getCurrUser() { + return AjaxJson.getSuccess() + .set("id", StpUtil.getLoginId()) + .set("user", StpUtil.getSession().get("user")); +} +``` + + + +``` java +// 从上下文获取当前登录 User 信息 +@RequestMapping("getCurrUser") +public AjaxJson getCurrUser() { + Subject subject = SecurityUtils.getSubject(); + SysUser sysUser = (SysUser)subject.getPrincipal(); + return AjaxJson.getSuccess() + .set("id", sysUser.getId()) + .set("user", sysUser); +} +``` + + + + + +### 从 session 上存取值 + + +``` java +// 测试从 session 上存取值 +@RequestMapping("testSession") +public AjaxJson test() { + SaSession session = StpUtil.getSession(); + + System.out.println("从 session 上取值:" + session.get("name")); + session.set("name", "zhang"); + System.out.println("从 session 上取值:" + session.get("name")); + + return AjaxJson.getSuccess(); +} +``` + + + +``` java +// 测试从 session 上存取值 +@RequestMapping("testSession") +public AjaxJson test() { + Subject subject = SecurityUtils.getSubject(); + Session session = subject.getSession(); + + System.out.println("从 session 上取值:" + session.getAttribute("name")); + session.setAttribute("name", "zhang"); + System.out.println("从 session 上取值:" + session.getAttribute("name")); + + return AjaxJson.getSuccess(); +} +``` + + + + + + + + +### 角色认证 & 权限认证 + + + + +自定义 StpInterface 实现类 + +``` java +@Component +public class StpInterfaceImpl implements StpInterface { + + // 加载角色信息 + @Override + public List getPermissionList(Object loginId, String loginType) { + return Arrays.asList("admin", "super-admin", "ceo"); + } + + // 加载权限信息 + @Override + public List getRoleList(Object loginId, String loginType) { + return Arrays.asList("user:add", "user:delete", "user:update"); + } + +} +``` + +测试 Controller +``` java +@RestController +@RequestMapping("/jur/") +public class JurController { + + // 角色判断 ---- http://localhost:8082/jur/assertRole + @RequestMapping("assertRole") + public AjaxJson assertRole() { + // is 模式,返回 true 或 false + System.out.println("单个权限判断:" + StpUtil.hasRole("admin")); + System.out.println("多个权限判断(and):" + StpUtil.hasRoleAnd("admin", "dev-admin")); + System.out.println("多个权限判断(or):" + StpUtil.hasRoleOr("admin", "dev-admin")); + + // check 模式,无角色时抛出异常 + StpUtil.checkRole("admin"); // 单个 check + StpUtil.checkRoleAnd("admin", "dev-admin"); // 多个 check (and) + StpUtil.checkRoleOr("admin", "dev-admin"); // 多个 check (or) + + return AjaxJson.getSuccess(); + } + + // 权限判断 ---- http://localhost:8082/jur/assertPermission + @RequestMapping("assertPermission") + public AjaxJson assertPermission() { + // is 模式,返回 true 或 false + System.out.println("单个权限判断:" + StpUtil.hasPermission("user:add")); + System.out.println("多个权限判断(and):" + StpUtil.hasPermissionAnd("user:add", "user:delete22")); + System.out.println("多个权限判断(or):" + StpUtil.hasPermissionOr("user:add", "user:delete22")); + + // check 模式,无权限时抛出异常 + StpUtil.checkPermission("user:add"); // 单个 check + StpUtil.checkPermissionAnd("user:add", "user:delete22"); // 多个 check (and) + StpUtil.checkPermissionOr("user:add", "user:delete22"); // 多个 check (or) + + return AjaxJson.getSuccess(); + } + +} +``` + + + + +自定义 Realm 里重写方法 doGetAuthorizationInfo + +``` java +@Override +protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); + // 加载角色信息 + authorizationInfo.addRoles(Arrays.asList("admin", "super-admin", "ceo")); + // 加载权限信息 + authorizationInfo.addStringPermissions(Arrays.asList("user:add", "user:delete", "user:update")); + return authorizationInfo; +} +``` + +测试 Controller +``` java +@RestController +@RequestMapping("/jur/") +public class JurController { + + // 角色判断 + @RequestMapping("assertRole") + public AjaxJson assertRole() { + Subject subject = SecurityUtils.getSubject(); + + // is 模式,返回 true 或 false + System.out.println("单个权限判断:" + subject.hasRole("admin")); + System.out.println("多个权限判断(and):" + subject.hasAllRoles(Arrays.asList("admin", "dev-admin"))); + System.out.println("多个权限判断(or):" + (subject.hasRole("admin") || subject.hasRole("dev-admin"))); + + // check 模式,无角色时抛出异常 + subject.checkRole("admin"); // 单个 check + subject.checkRoles("admin", "dev-admin"); // 多个 check (and) + + return AjaxJson.getSuccess(); + } + + // 权限判断 + @RequestMapping("assertPermission") + public AjaxJson assertPermission() { + Subject subject = SecurityUtils.getSubject(); + + // is 模式,返回 true 或 false + System.out.println("单个权限判断:" + subject.isPermitted("user:add")); + System.out.println("多个权限判断(and):" + subject.isPermittedAll("user:add", "user:delete22")); + System.out.println("多个权限判断(or):" + (subject.isPermitted("user:add") || subject.isPermitted("user:delete22"))); + + // check 模式,无权限时抛出异常 + subject.checkPermission("user:add"); // 单个 check + subject.checkPermissions("user:add", "user:delete22"); // 多个 check (and) + + return AjaxJson.getSuccess(); + } + +} +``` + + + + + + +### 注解鉴权 + + + + +SaTokenConfigure 配置注解拦截器 +``` java +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); + } +} +``` + +测试 Controller +``` java +@RestController +@RequestMapping("/at-check/") +public class AtCheckController { + + // 登录校验 + @SaCheckLogin + @RequestMapping("checkLogin") + public AjaxJson checkLogin() { + return AjaxJson.getSuccess(); + } + + // 角色校验 + @SaCheckRole("admin") + @RequestMapping("checkRole") + public AjaxJson checkRole() { + return AjaxJson.getSuccess(); + } + + // 权限校验 + @SaCheckPermission("user:add") + @RequestMapping("checkPermission") + public AjaxJson checkPermission() { + return AjaxJson.getSuccess(); + } + + // 忽略认证校验 + @SaIgnore + @SaCheckLogin + @RequestMapping("ignoreCheck") + public AjaxJson ignoreCheck() { + return AjaxJson.getSuccess(); + } + +} +``` + + + + +测试 Controller +``` java +@RestController +@RequestMapping("/at-check/") +public class AtCheckController { + + // 登录校验 + @RequiresAuthentication + @RequestMapping("checkLogin") + public AjaxJson checkLogin() { + return AjaxJson.getSuccess(); + } + + // 角色校验 + @RequiresRoles("admin") + @RequestMapping("checkRole") + public AjaxJson checkRole() { + return AjaxJson.getSuccess(); + } + + // 权限校验 + @RequiresPermissions("user:add") + @RequestMapping("checkPermission") + public AjaxJson checkPermission() { + return AjaxJson.getSuccess(); + } + +} +``` + + + + + +### 路由拦截鉴权 + + + +SaTokenConfigure 配置 +``` java +@Override +public void addInterceptors(InterceptorRegistry registry) { + // 注册 Sa-Token 拦截器打开注解鉴权功能 + registry.addInterceptor(new SaInterceptor(handle -> { + SaRouter.match("/route-check/getInfo1").stop(); // 不拦截 + SaRouter.match("/route-check/getInfo2").check(r -> StpUtil.checkLogin()); // 需要登录 + SaRouter.match("/route-check/getInfo3").check(r -> StpUtil.checkRole("admin2")); // 需要角色 + SaRouter.match("/route-check/getInfo4").check(r -> StpUtil.checkPermission("user:add3")); // 需要权限 + })).addPathPatterns("/**"); +} +``` + +鉴权未通过时处理方案 +``` java +@RestControllerAdvice +public class GlobalException { + + @ExceptionHandler(NotLoginException.class) + public AjaxJson handlerException(NotLoginException e) { + return AjaxJson.get(401, "未登录"); + } + + @ExceptionHandler(NotRoleException.class) + public AjaxJson handlerException(NotRoleException e) { + return AjaxJson.get(403, "缺少角色:" + e.getRole()); + } + + @ExceptionHandler(NotPermissionException.class) + public AjaxJson handlerException(NotPermissionException e) { + return AjaxJson.get(403, "缺少权限:" + e.getPermission()); + } + +} +``` + + + +过滤器配置 +``` java +@Bean +public ShiroFilterFactoryBean shiroFilterFactoryBean() { + ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); + bean.setSecurityManager(securityManager()); + + // 路由拦截鉴权 + Map filterMap = new LinkedHashMap<>(); + filterMap.put("/route-check/getInfo", "anon"); // 不拦截 + filterMap.put("/route-check/getInfo2", "authc"); // 需要登录 + filterMap.put("/route-check/getInfo3", "perms[admin2]"); // 需要角色 + filterMap.put("/route-check/getInfo4", "perms[user:add3]"); // 需要权限 + bean.setFilterChainDefinitionMap(filterMap); + bean.setLoginUrl("/401"); // 未登录时跳转的 url + bean.setUnauthorizedUrl("/403"); // 未授权时跳转的 url + + return bean; +} +``` + +鉴权未通过时处理方案 +``` java +@RestController +public class ShiroErrorController { + + @RequestMapping("/401") + public Object error401(HttpServletRequest request, HttpServletResponse response) { + response.setStatus(200); + return AjaxJson.get(401, "not login"); + } + + @RequestMapping("/403") + public Object error403(HttpServletRequest request, HttpServletResponse response) { + response.setStatus(200); + return AjaxJson.get(403, "鉴权未通过"); + } + +} +``` + + + + + +### 和 Thymeleaf 集成 + + + + +pom.xml 依赖 +``` xml + + + cn.dev33 + sa-token-dialect-thymeleaf + ${sa-token.version} + +``` + +SaTokenConfigure 增加配置 Sa-Token 标签方言对象 + +``` java +// Sa-Token 标签方言 (Thymeleaf版) +@Bean +public SaTokenDialect getSaTokenDialect() { + return new SaTokenDialect(); +} +``` + +新建 ThymeleafConfigure 注入全局变量 +``` java +@Configuration +public class ThymeleafConfigure { + // 为 Thymeleaf 注入全局变量,以便在页面中调用 Sa-Token 的方法 + @Autowired + public void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) { + viewResolver.addStaticVariable("stp", StpUtil.stpLogic); + } +} +``` + +新建 Controller +``` java +@Controller +public class HomeController { + @RequestMapping("/") + public Object index(HttpServletRequest request) { + request.setAttribute("isLogin", StpUtil.isLogin()); + return new ModelAndView("index.html"); + } +} +``` + +新建 templates/index.html +``` html + + + + Sa-Token 集成 Thymeleaf 标签方言 + + + + +
+

Sa-Token 集成 Thymeleaf 标签方言 —— 测试页面

+

当前是否登录:

+

+ 登录 + 注销 +

+ +

登录之后才能显示:value

+

不登录才能显示:value

+ +

具有角色 admin 才能显示:value

+

同时具备多个角色才能显示:value

+

只要具有其中一个角色就能显示:value

+

不具有角色 admin 才能显示:value

+ +

具有权限 user-add 才能显示:value

+

同时具备多个权限才能显示:value

+

只要具有其中一个权限就能显示:value

+

不具有权限 user-add 才能显示:value

+ +

+ 从SaSession中取值: + +

+ +
+ + +``` + + + + +pom.xml 依赖 +``` xml + + + com.github.theborakompanioni + thymeleaf-extras-shiro + 2.1.0 + +``` + +ShiroConfigure 增加配置 Shiro 方言对象 +``` java +@Bean +public ShiroDialect shiroDialect() { + return new ShiroDialect(); +} +``` + +新建 Controller +``` java +@Controller +public class HomeController { + @RequestMapping("/") + public Object index(HttpServletRequest request) { + Subject subject = SecurityUtils.getSubject(); + request.setAttribute("isLogin", subject.isAuthenticated()); + return new ModelAndView("index.html"); + } +} +``` + +新建 templates/index.html + +``` html + + + + Shiro 集成 Thymeleaf 标签方言 + + + + +
+

Shiro 集成 Thymeleaf 标签方言 —— 测试页面

+

当前是否登录:

+

+ 登录 + 注销 +

+

登录之后才能显示:value

+

不登录才能显示:value

+ +

具有角色 admin 才能显示:value

+

同时具备多个角色才能显示:value

+

只要具有其中一个角色就能显示:value

+

不具有角色 admin 才能显示:value

+ +

具有权限 user-add 才能显示:value

+

同时具备多个权限才能显示:value

+

只要具有其中一个权限就能显示:value

+

不具有权限 user-add 才能显示:value

+ +

+ 当前登录账号: +

+ +
+ + +``` + + + + + +### 前后端分离 + + +1、在登录时,将 token 信息返回到前端 +``` java +// 测试登录 +@RequestMapping("doLogin") +public AjaxJson doLogin(String username, String password) { + // 校验 + SysUser user = sysUserDao.findByUsername(username); + // user 信息校验代码不再赘述 ... + + // 登录 + StpUtil.login(user.getId()); + StpUtil.getSession().set("user", user); + return AjaxJson.getSuccess("登录成功").set("satoken", StpUtil.getTokenValue()); // 关键代码 +} +``` + +2、前端改造 +- 1、在登录请求时,将返回的 token 保存到本地 `localStorage.setItem('satoken', res.satoken)`。 +- 2、在后续每次请求中,读取本地保存的 satoken 塞到请求 header 中 + +``` js +const header = {}; +if(localStorage.satoken) { + header.satoken = localStorage.satoken; +} +// 后续提交请求... +``` + + + + +1、自定义 SessionManager,从请求 header 里读取前端提交的 token +``` java +public class MySessionManager extends DefaultWebSessionManager { + + private static final String TOKEN = "token"; + + private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; + + public MySessionManager() { + super(); + } + + @Override + protected Serializable getSessionId(ServletRequest request, ServletResponse response) { + String id = WebUtils.toHttp(request).getHeader(TOKEN); + // 如果请求头中有 token 则其值为sessionId + if (!StringUtils.isEmpty(id)) { + request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); + 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); + } + } +} +``` + +2、注入到 SecurityManager 中 +``` java +@Configuration +public class ShiroConfigure { + + // 省略其它次要代码 ... + + @Bean + public DefaultWebSecurityManager securityManager() { + DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); + manager.setRealm(myRealm()); + manager.setSessionManager(sessionManager()); + return manager; + } + + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean() { + ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); + bean.setSecurityManager(securityManager()); + return bean; + } + + // 自定义sessionManager + @Bean + public SessionManager sessionManager() { + MySessionManager mySessionManager = new MySessionManager(); + return mySessionManager; + } +} +``` + +3、测试 Controller,登录时将 token 信息返回到前端 +``` java +// 测试登录 +@RequestMapping("doLogin") +public AjaxJson doLogin(String username, String password) { + Subject subject = SecurityUtils.getSubject(); + try { + subject.login(new UsernamePasswordToken(username, password)); + String token = subject.getSession().getId().toString(); // 关键代码 + return AjaxJson.getSuccess("登录成功!").set("token", token); // 关键代码 + } catch (AuthenticationException e) { + e.printStackTrace(); + return AjaxJson.getError(e.getMessage()); + } +} +``` + +4、前端改造 +- 1、在登录请求时,将返回的 token 保存到本地 `localStorage.setItem('token', res.token)`。 +- 2、在后续每次请求中,读取本地保存的 token 塞到请求 header 中 + +``` js +const header = {}; +if(localStorage.token) { + header.token = localStorage.token; +} +// 后续提交请求... +``` + + + + + + +### 集成 Redis + + + + +pom.xml 引入依赖 +``` xml + + + cn.dev33 + sa-token-redis-jackson + ${sa-token.version} + + + + + org.apache.commons + commons-pool2 + +``` + +application.yml 新增连接配置 +``` yaml +spring: + data: + # redis配置 + redis: + # Redis数据库索引(默认为0) + database: 1 + # Redis服务器地址 + host: 127.0.0.1 + # Redis服务器连接端口 + port: 6379 + # Redis服务器连接密码(默认为空) + password: + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池最大连接数 + max-active: 200 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + # 连接池中的最大空闲连接 + max-idle: 10 + # 连接池中的最小空闲连接 + min-idle: 0 +``` + +其它代码照旧 + + + +pom.xml 引入依赖 + +``` xml + + + org.crazycake + shiro-redis + 3.3.1 + +``` + +application.yml 新增连接配置 +``` yaml + +spring: + redis: + shiro: + # Redis服务器地址 + host: 127.0.0.1:6379 + # Redis服务器连接密码(默认为空) + password: + # Redis数据库索引(默认为0) + database: 2 + # 连接超时时间 + timeout: 1800 + +``` + + +ShiroConfigure 注入相关 Bean +``` java +@Configuration +public class ShiroConfigure { + + // 自定义 securityManager + @Bean + public DefaultWebSecurityManager securityManager() { + DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); + // manager.setRealm(myRealm()); + + // 自定义session管理 使用redis + manager.setSessionManager(sessionManager()); + // 自定义缓存实现 使用redis + manager.setCacheManager(cacheManager()); + + return manager; + } + + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean() { + ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); + bean.setSecurityManager(securityManager()); + return bean; + } + + // -------- 以下为 shiro redis 相关 -------- + + // Shiro redis 连接信息 + @Value("${spring.redis.shiro.host}") + private String host; + @Value("${spring.redis.shiro.database}") + private int database; + @Value("${spring.redis.shiro.timeout}") + private int timeout; + @Value("${spring.redis.shiro.password}") + private String password; + + /** + * 配置shiro redisManager + */ + public RedisManager redisManager() { + RedisManager redisManager = new RedisManager(); + redisManager.setHost(host); + if(StringUtils.hasText(password)){ + redisManager.setPassword(password); + } + redisManager.setDatabase(database); + redisManager.setTimeout(timeout); + return redisManager; + } + + /** + * cacheManager 缓存 redis 实现 + */ + @Bean + public RedisCacheManager cacheManager() { + RedisCacheManager redisCacheManager = new RedisCacheManager(); + redisCacheManager.setRedisManager(redisManager()); + return redisCacheManager; + } + + /** + * RedisSessionDAO redis 实现 + */ + @Bean + public RedisSessionDAO redisSessionDAO() { + RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); + redisSessionDAO.setRedisManager(redisManager()); + return redisSessionDAO; + } + + // 自定义sessionManager + @Bean + public SessionManager sessionManager() { + MySessionManager mySessionManager = new MySessionManager(); + mySessionManager.setSessionDAO(redisSessionDAO()); + return mySessionManager; + } + +} +``` + +SysUser 实体类要实现 Serializable 接口 +``` java +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SysUser implements Serializable { + // ... +} +``` + +其它代码照旧 + + + + + + + + + + + + +