
本文共 25099 字,大约阅读时间需要 83 分钟。
文章目录
1. 下载
- 选择Source Code Distribution,下载,解压
2. 测试
实际开发使用注解的方式,搭建web开发环境,不会在ini配置文件进行硬编码的操作
- 测试样例参考
shiro解压目录\samples\quickstart
- 需要的jar包
shiro-all.jarlog4j.jarslf4j-api.jarslf4j-log4j12.jar
- 官方shiro.ini文件(截取部分文件)
[users]# 用户 'root' 密码 'secret' 角色 'admin' root = secret, admin# 用户 'guest' 密码 'guest' 角色 'guest' guest = guest, guest# 用户 'darkhelmet' 密码 'ludicrousspeed' 角色 'darklord' and 'schwartz'darkhelmet = ludicrousspeed, darklord, schwartz[roles]# 'admin' role has all permissions, indicated by the wildcard '*'admin = *# The 'schwartz' role can do anything (*) with any lightsaber:schwartz = lightsaber:*# 用户名 = 类型-用户:操作-删除:实例-张三--->goodgay 可以删除张三的用户信息# 主:谓:宾goodguy = user:delete:zhangsan
- 官方Quickstart.java:\samples\quickstart\src\main\java\Quickstart.java
public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main(String[] args) { // 使用ini配置文件配置realms, users, roles and permissions,创建Shiro SecurityManager Factoryfactory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); /** * 如何操作shiro 模拟用户、密码、角色、权限都在ini配置文件里 */ // 1. 获取当前的 Subject. 调用 SecurityUtils.getSubject(); Subject currentUser = SecurityUtils.getSubject(); // 获取 Session: Subject#getSession() Session session = currentUser.getSession(); session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey"); if (value.equals("aValue")) { log.info("---> Retrieved the correct value! [" + value + "]"); } // 2. 测试当前的用户是否已经被认证. 即是否已经登录. // 调动 Subject 的 isAuthenticated() if (!currentUser.isAuthenticated()) { // 把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); // rememberme token.setRememberMe(true); try { // 执行登录. currentUser.login(token); } // 若没有指定的账户, 则 shiro 将会抛出 UnknownAccountException 异常. catch (UnknownAccountException uae) { log.info("----> There is no user with username of " + token.getPrincipal()); return; } // 若账户存在, 但密码不匹配, 则 shiro 会抛出 IncorrectCredentialsException 异常。 catch (IncorrectCredentialsException ice) { log.info("----> Password for account " + token.getPrincipal() + " was incorrect!"); return; } // 用户被锁定的异常 LockedAccountException catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } // 所有认证时异常的父类. catch (AuthenticationException ae) { //unexpected condition? error? } } //say who they are: //print their identifying principal (in this case, a username): log.info("----> User [" + currentUser.getPrincipal() + "] logged in successfully."); // 测试是否有某一个角色. 调用 Subject 的 hasRole 方法. if (currentUser.hasRole("schwartz")) { log.info("----> May the Schwartz be with you!"); } else { log.info("----> Hello, mere mortal."); return; } // 测试用户是否具备某一个行为. 调用 Subject 的 isPermitted() 方法。 if (currentUser.isPermitted("lightsaber:weild")) { log.info("----> You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } // 测试用户是否具备某一个行为. if (currentUser.isPermitted("user:delete:zhangsan")) { log.info("----> You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } // 执行登出. 调用 Subject 的 Logout() 方法. 通过测试用户是否被认证来查看用户是否登陆退出 System.out.println("---->" + currentUser.isAuthenticated()); currentUser.logout(); System.out.println("---->" + currentUser.isAuthenticated()); System.exit(0); }}
3. SSM整合教程
3.1 基本认证流程
- 获取当前的 Subject,调用 SecurityUtils.getSubject()
- 测试当前的用户是否已经被认证. 即是否已经登录,调用 Subject 的 isAuthenticated() 方法
- 如果没有被认证,则把用户名和密码(通过将表单信息提交到SpringMVC中)封装为 UsernamePasswordToken 对象
- 执行登录,调用 Subject 的 login(token) 方法
- 自定义Realm,继承 AuthenticatingRealm,实现 doGetAuthenticationInfo(),通过数据库获取对应记录,返回给shiro
- 通过shiro完成密码比对:前台输入的 UsernamePasswordToken 与 数据库查询的 SimpleAuthenticationInfo 信息进行比对
3.2 基本配置
- 依赖管理:修改pom.xml
org.apache.shiro shiro-core 1.2.4 org.apache.shiro shiro-web 1.2.4 org.apache.shiro shiro-spring 1.2.4
- 修改web.xml
shiroFilter org.springframework.web.filter.DelegatingFilterProxy targetFilterLifecycle true shiroFilter /*
- 新建spring-shiro.xml
/login.jsp = anon /shiro/login = anon /shiro/logout = logout # everything else requires authentication: /** = authc
- 在spring.xml文件中导入spring-shiro.xml
- 自定义Realm
public class ShiroRealm extends AuthenticatingRealm{ /** * token:是SpringMVC从请求中获取的 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("token.hashCode() = " + token.hashCode()); //1. 把 AuthenticationToken 转换为 UsernamePasswordToken UsernamePasswordToken upToken = (UsernamePasswordToken) token; //2. 从 UsernamePasswordToken 中来获取 username String username = upToken.getUsername(); //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录 System.out.println("用户名:"+username); //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常 if("unknown".equals(username)){ throw new UnknownAccountException("用户不存在!"); } //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常 if("locked".equals(username)){ throw new LockedAccountException("用户被锁定!"); } //6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo //1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象 Object principal = username; //2). credentials: 从数据库中获取密码,以下是在本地模拟 Object credentials = "123456"; //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String realmName = getName(); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName); return info; }}
- login.jsp
- success.jsp
Logout
- ShiroController
@Controller@RequestMapping("/shiro")public class ShiroController { @RequestMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password){ Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { // 把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken(username, password); // rememberme token.setRememberMe(true); try { System.out.println("token.hashCode() = " + token.hashCode()); // 执行登录 currentUser.login(token); } // ... catch more exceptions here (maybe custom ones specific to your application? // 所有认证时异常的父类 catch (AuthenticationException ae) { //unexpected condition? error? System.out.println("登录失败: " + ae.getMessage()); } } return "redirect:/success.jsp"; } }
- 测试效果
- 测试访问其他页面:无法访问,返回login.jsp
- 测试用户名输入:unknown,打印:登录失败: 用户不存在!
- 测试输入用户名:aa、密码:123456(即定义的Realm中的密码),登陆成功
3.3 密码加密配置
- 将前台获取的密码进行加密:修改 spring-shiro.xml 配置的 Realm
- 修改自定义Realm
public class ShiroRealm extends AuthenticatingRealm{ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //前面不变.. //6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo //1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象 Object principal = username; //2). credentials: 密码 Object credentials = "038bdaf98f2037b31f1e75b5b4c9b26e"; //123456采用MD5加密后的结构 //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String realmName = getName(); //4). 盐值 ByteSource credentialsSalt = ByteSource.Util.bytes(username); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); return info; }}
- 原理
//shiro-core.jar包下package org.apache.shiro.authc;public class UsernamePasswordToken { public char[] getPassword() { return password; // 打断点 }}//不断下一步,会来到equals方法,进行密码比对package org.apache.shiro.authc.credential;public class HashedCredentialsMatcher extends SimpleCredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { Object tokenHashedCredentials = hashProvidedCredentials(token, info); Object accountCredentials = getCredentials(info); return equals(tokenHashedCredentials, accountCredentials); }}
3.4 配置多个Realm
- 增加新的自定义的Realm,使用SHA1加密,其他不变
public class ShiroRealm2 extends AuthenticatingRealm{ /** * token:是SpringMVC从请求中获取的 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("token.hashCode() = " + token.hashCode()); System.out.println("ShiroRealm2"); //1. 把 AuthenticationToken 转换为 UsernamePasswordToken UsernamePasswordToken upToken = (UsernamePasswordToken) token; //2. 从 UsernamePasswordToken 中来获取 username String username = upToken.getUsername(); //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录 System.out.println("用户名:"+username); //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常 if("unknown".equals(username)){ throw new UnknownAccountException("用户不存在!"); } //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常 if("locked".equals(username)){ throw new LockedAccountException("用户被锁定!"); } //6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo //1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象 Object principal = username; //2). credentials: 密码 Object credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06"; //123456 //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String realmName = getName(); //4). 盐值 ByteSource credentialsSalt = ByteSource.Util.bytes(username); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); return info; } }
- 修改spring-shiro.xml
- 原理
//controllercurrentUser.login(token);//调用Subject接口的login方法public interface Subject { void login(AuthenticationToken token) throws AuthenticationException;}//去找login的实现方法,这里调用securityManager.login()方法public class DelegatingSubject implements Subject { public void login(AuthenticationToken token) throws AuthenticationException { clearRunAsIdentitiesInternal(); Subject subject = securityManager.login(this, token); //...}//来到SecurityManager接口public interface SecurityManager extends Authenticator, Authorizer, SessionManager { Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;}//找对应实现类,调用authenticate()方法public class DefaultSecurityManager extends SessionsSecurityManager { public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { info = authenticate(token); } //... }}//继续进入public abstract class AuthenticatingSecurityManager extends RealmSecurityManager { public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { return this.authenticator.authenticate(token); }}//找authenticate()方法实现类,这里执行doAuthenticate()方法public abstract class AbstractAuthenticator implements Authenticator, LogoutAware { public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { //... AuthenticationInfo info; try { info = doAuthenticate(token); //... }}//找doAuthenticate()实现方法public abstract class AbstractAuthenticator implements Authenticator, LogoutAware { protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token) throws AuthenticationException;}//ModularRealmAuthenticator.class中getRealms()获取到的是Realm的集合public class ModularRealmAuthenticator extends AbstractAuthenticator { //263行 protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { assertRealmsConfigured(); Collectionrealms = getRealms(); if (realms.size() == 1) { //单个Realms认证 return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken); } else { //多个Realms认证 return doMultiRealmAuthentication(realms, authenticationToken); } } //198行 protected AuthenticationInfo doMultiRealmAuthentication(Collection realms, AuthenticationToken token) { //选择认证策略 AuthenticationStrategy strategy = getAuthenticationStrategy(); AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token); //...}
3.5 授权
- 增加user.jsp
- 增加admin.jsp
- 修改success.jsp
成功!
Logout User Page Admin page
- 修改自定义Realm:模拟分别让user、admin登陆系统,密码都是123456
public class ShiroRealm extends AuthenticatingRealm{ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //... Object credentials = null; if("admin".equals(username)){ credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";//123456加盐:admin、加密:MD5 }else if("user".equals(username)){ credentials = "098d2c478e9c11555ce2823231e02ec1";//123456加盐:user、加密:MD5 } //... }}
- 测试:登陆(用户名:user、密码:123456),登陆成功,可以访问user.jsp、admin.jsp
- 修改spring-shiro.xml,设置user.jsp、admin.jsp的访问权限
/login.jsp = anon /shiro/login = anon /shiro/logout = logout /user.jsp = roles[user] /admin.jsp = roles[admin] # everything else requires authentication: /** = authc
- 测试:登陆(用户名:user、密码:123456),登陆成功,不可以访问user.jsp、admin.jsp,会跳转到unauthorized.jsp
- 通过以下原理分析,得出自定义Realm需要实现AuthorizingRealm,之前实现的是AuthenticatingRealm,所以修改ShiroRealm.java
public class ShiroRealm extends AuthorizingRealm{ /** * 1.认证方法 * token:是SpringMVC从请求中获取的 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("token.hashCode() = " + token.hashCode()); //1. 把 AuthenticationToken 转换为 UsernamePasswordToken UsernamePasswordToken upToken = (UsernamePasswordToken) token; //2. 从 UsernamePasswordToken 中来获取 username String username = upToken.getUsername(); //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录 System.out.println("用户名:"+username); //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常 if("unknown".equals(username)){ throw new UnknownAccountException("用户不存在!"); } //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常 if("locked".equals(username)){ throw new LockedAccountException("用户被锁定!"); } //6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo //1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象 Object principal = username; //2). credentials: 从数据库中获取密码,以下是在本地模拟 Object credentials = null; if("admin".equals(username)){ credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";//123456加盐:admin、加密:MD5 }else if("user".equals(username)){ credentials = "098d2c478e9c11555ce2823231e02ec1";//123456加盐:user、加密:MD5 } //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String realmName = getName(); //4). 盐值 ByteSource credentialsSalt = ByteSource.Util.bytes(username); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); return info; } /** * 2.授权会被shiro回调的方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //1. 从 PrincipalCollection 中来获取登录用户的信息 Object principal = principals.getPrimaryPrincipal(); //2. 利用登录用户的信息获取当前用户的角色或权限(可能需要查询数据库) Setroles = new HashSet<>(); roles.add("user"); // 模拟:所有用户都被赋予user角色 if("admin".equals(principal)){ roles.add("admin"); // 模拟:只有admin登陆后才有admin角色 } //3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles); //4. 返回 SimpleAuthorizationInfo 对象. return info; }}
- 测试
- 登陆【user、123456】,只能访问user.jsp
- 登陆【admin、123456】,能访问user.jsp、admin.jsp
- 原理(分析得出,自定义Realm需要实现AuthorizingRealm的doGetAuthorizationInfo方法)
//判断是否有权限:调用Subject.class下的hasRole()方法public interface Subject { boolean hasRole(String roleIdentifier);}//hasRole()的实现方法:调用securityManager的hasRole()方法public class DelegatingSubject implements Subject { public boolean hasRole(String roleIdentifier) { return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier); }}//执行AuthorizingSecurityManager.class下的hasRole()方法public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager { public boolean hasRole(PrincipalCollection principals, String roleIdentifier) { return this.authorizer.hasRole(principals, roleIdentifier); }}//来到ModularRealmAuthorizer.class下的hasRole()方法public class ModularRealmAuthorizer implements Authorizer, PermissionResolverAware, RolePermissionResolverAware { public boolean hasRole(PrincipalCollection principals, String roleIdentifier) { assertRealmsConfigured();//验证realms不为空 for (Realm realm : getRealms()) { if (!(realm instanceof Authorizer)) continue; if (((Authorizer) realm).hasRole(principals, roleIdentifier)) { return true; } } return false; }}//这里将realm强转为Authorizer,去调用Authorizer接口下的hasRole()方法public interface Authorizer { boolean hasRole(PrincipalCollection subjectPrincipal, String roleIdentifier);}//hasRole()方法的具体实现方法为AuthorizingRealm.class下的hasRole()方法public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware { //572行,hasRole()方法执行getAuthorizationInfo public boolean hasRole(PrincipalCollection principal, String roleIdentifier) { AuthorizationInfo info = getAuthorizationInfo(principal); return hasRole(roleIdentifier, info); } //310行,执行doGetAuthorizationInfo protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) { //... if (info == null) { info = doGetAuthorizationInfo(principals); //... } } //399行,该方法是一个抽象方法,需要被实现【重要!】 protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals); //577行,执行hasRole protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) { return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier); }}
3.6 权限注解
@RequiresAuthentication
表示当前Subject已经通过login 进行了身份验证;即Subject. isAuthenticated()返回true。
@RequiresUser
表示当前Subject已经身份验证或者通过记住我登录的。
@RequiresGuest
表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)
@RequiresRoles(value={“admin”}) @RequiresRoles({“admin“})
表示当前Subject需要角色admin 和user。
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)
表示当前Subject需要权限user:a或user:b。
3.6.1 不使用@Service注解
public class ShiroService { @RequiresRoles({ "admin"}) public void testMethod(){ System.out.println("testMethod, time: " + new Date()); } }
spring.xml中将ShiroService添加到IOC容器
测试注解使用:必须角色admin才能执行testMethod();如果以【user、123456】登陆,无法执行testMethod(),会报错Subject does not have role [admin]
;以【admin、123456】登陆,可以执行testMethod()
3.6.2 使用@Service注解
坑:shiro注解不生效原因:官方说明shiro和spring集成时,spring-shiro.xml需要被spring.xml引用,但是集成springmvc时,spring-shiro.xml中关于开启注解的部分需要声明在spring-mvc.xml中
@Servicepublic class ShiroService { @RequiresRoles({ "admin"}) public void testMethod(){ System.out.println("testMethod, time: " + new Date()); } }
3.6.3 使用Spring声明式异常处理没有权限的异常
ExceptionHandlerControllerAdvice
3.7 从数据库中获取资源和权限
之前采用的方式:在spring-shiro.xml中配置
//... /login.jsp = anon /shiro/login = anon /shiro/logout = logout /user.jsp = roles[user] /admin.jsp = roles[admin] # everything else requires authentication: /** = authc
实际上是调用了ShiroFilterFactoryBean.class的setFilterChainDefinitions(String definitions)方法,而经过debug,发现setFilterChainDefinitionMap()方法传入的是LinkedHashMap,即将上述filterChainDefinitions中的values封装到Map中。
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor { public void setFilterChainDefinitions(String definitions) { Ini ini = new Ini(); ini.load(definitions); //did they explicitly state a 'urls' section? Not necessary, but just in case: Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS); if (CollectionUtils.isEmpty(section)) { //no urls section. Since this _is_ a urls chain definition property, just assume the //default section contains only the definitions: section = ini.getSection(Ini.DEFAULT_SECTION_NAME); } setFilterChainDefinitionMap(section); } public void setFilterChainDefinitionMap(MapfilterChainDefinitionMap) { this.filterChainDefinitionMap = filterChainDefinitionMap; }}
所以优化spring-shiro.xml:
//...
新建FilterChainDefinitionMapBuilder.java
public class FilterChainDefinitionMapBuilder { public LinkedHashMapbuildFilterChainDefinitionMap(){ LinkedHashMap map = new LinkedHashMap<>(); map.put("/login.jsp", "anon"); map.put("/shiro/login", "anon"); map.put("/shiro/logout", "logout"); //访问user.jsp,需要经过认证authc,并且还具有user权限 map.put("/user.jsp", "authc,roles[user]"); map.put("/admin.jsp", "authc,roles[admin]"); //如果开启rememberme,只需要有user角色就可以访问,无需认证 map.put("/success.jsp", "user"); map.put("/**", "authc"); return map; } }
3.8 会话管理
3.9 缓存管理
3.10 Remember Me
controller:设置token.setRememberMe(true);
@Controller@RequestMapping("/shiro")public class ShiroController { @RequestMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password){ Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { // 把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken(username, password); // rememberme token.setRememberMe(true); //... } }
spring-shiro.xml:增加设置rememberme失效时间
4. SpringBoot整合教程
附录1:新建Spring工程
附录2:新建SpringBoot工程
发表评论
最新留言
关于作者
