SpringBoot整合Shiro实现权限控制,验证码,记住我

本文介绍 SpringBoot 整合 shiro,相对于 Spring Security 而言,shiro 更加简单,没有那么复杂。

目前我的需求是一个博客系统,有用户和管理员两种角色。一个用户可能有多个角色,每个角色可能有多个权限,每个角色关联不同的菜单(也可以权限和菜单关联)。

本文主要介绍 Shiro 的使用,这里只介绍用户和角色,不需要权限也行。

一、数据库设计

三张表:user、role、user_role

  1. -- ----------------------------
  2. --  Table structure for `role`
  3. -- ----------------------------
  4. DROP TABLE IF EXISTS `role`;
  5. CREATE TABLE `role` (
  6.   `id` int(11) NOT NULL AUTO_INCREMENT,
  7.   `description` varchar(255) DEFAULT NULL,
  8.   `role` varchar(255) DEFAULT NULL,
  9.   PRIMARY KEY (`id`)
  10. ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
  11. -- ----------------------------
  12. --  Table structure for `user`
  13. -- ----------------------------
  14. DROP TABLE IF EXISTS `user`;
  15. CREATE TABLE `user` (
  16.   `id` int(10) NOT NULL AUTO_INCREMENT,
  17.   `passwordvarchar(100) NOT NULL,
  18.   `username` varchar(20) NOT NULL COMMENT '用于登录的账号',
  19.   `display_name` varchar(20) DEFAULT NULL COMMENT '显示的用户名',
  20.   `email` varchar(100) DEFAULT NULL COMMENT '电子邮箱',
  21.   `url` varchar(255) DEFAULT NULL COMMENT '个人主页',
  22.   `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
  23.   `profile` varchar(255) DEFAULT NULL COMMENT '简介',
  24.   `create_time` datetime DEFAULT NULL,
  25.   `last_login_time` datetime DEFAULT NULL,
  26.   `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '正常1,禁止登录0, 已删除-1',
  27.   PRIMARY KEY (`id`),
  28.   UNIQUE KEY `uq_user_username` (`username`) USING BTREE,
  29.   UNIQUE KEY `uq_user_displayname` (`display_name`) USING BTREE,
  30.   UNIQUE KEY `uq_user_email` (`email`) USING BTREE
  31. ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
  32. -- ----------------------------
  33. --  Table structure for `user_role`
  34. -- ----------------------------
  35. DROP TABLE IF EXISTS `user_role`;
  36. CREATE TABLE `user_role` (
  37.   `role_id` int(11) NOT NULL,
  38.   `user_id` int(11) NOT NULL,
  39.   KEY `role_id` (`role_id`),
  40.   KEY `user_id` (`user_id`)
  41. ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
  42. SET FOREIGN_KEY_CHECKS = 1;

 

二、SpringBoot 整合 Shiro

1、添加 shiro 依赖

  1. <!--Shiro-->
  2. <dependency>
  3.     <groupId>org.apache.shiro</groupId>
  4.     <artifactId>shiro-spring</artifactId>
  5.     <version>1.4.0</version>
  6. </dependency>

 

2、MyShiroRealm.java

  1. package com.liuyanzhao.blog.web.config;
  2. import com.liuyanzhao.blog.api.model.Role;
  3. import com.liuyanzhao.blog.api.model.User;
  4. import com.liuyanzhao.blog.api.service.RoleService;
  5. import com.liuyanzhao.blog.api.service.UserService;
  6. import com.liuyanzhao.blog.api.util.Response;
  7. import com.liuyanzhao.blog.web.enums.UserStatus;
  8. import org.apache.shiro.authc.*;
  9. import org.apache.shiro.authz.AuthorizationInfo;
  10. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  11. import org.apache.shiro.realm.AuthorizingRealm;
  12. import org.apache.shiro.subject.PrincipalCollection;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import java.util.List;
  15. /**
  16.  * @author 言曌
  17.  * @date 2018/9/1 上午10:47
  18.  */
  19. public class MyShiroRealm extends AuthorizingRealm {
  20.     @Autowired
  21.     private UserService userService;
  22.     @Autowired
  23.     private RoleService roleService;
  24.     public static final String SALT = "com.liuyanzhao";
  25.     @Override
  26.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  27.         System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
  28.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
  29.         User user = (User) principals.getPrimaryPrincipal();
  30.         List<Role> roleList = roleService.listRolesByUser(user);
  31.         for (Role role : roleList) {
  32.             authorizationInfo.addRole(role.getRole());
  33.         }
  34.         return authorizationInfo;
  35.     }
  36.     @Override
  37.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
  38.             throws AuthenticationException {
  39.         System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
  40.         //获取用户的输入的账号.
  41.         String username = (String) token.getPrincipal();
  42.         System.out.println(token.getCredentials());
  43.         //通过username从数据库中查找 User对象,如果找到,没找到.
  44.         //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
  45.         Response<User> response = userService.getUserByUsername(username);
  46.         if (!response.getSuccess()) {
  47.             return null;
  48.         }
  49.         User user = response.getData();
  50.         if (UserStatus.LOCKED.getCode().equals(user.getStatus())) {
  51.             throw new LockedAccountException(username + "账号被锁定,请联系管理员!");
  52.         }
  53.         SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
  54.                 user,
  55.                 user.getPassword(),
  56.                 getName()
  57.         );
  58.         return authenticationInfo;
  59.     }
  60. }

这个是自定义验证账号密码和验证是否有权限。

其中 UserService 和 RoleService 这里就不用给了,一个是根据用户名获得用户,一个是根据获得用户的权限列表。

 

3、ShiroConfig.java

  1. package com.liuyanzhao.blog.web.config;
  2. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  3. import org.apache.shiro.mgt.SecurityManager;
  4. import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  5. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  6. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
  10. import java.util.LinkedHashMap;
  11. import java.util.Map;
  12. import java.util.Properties;
  13. /**
  14.  * @author 言曌
  15.  * @date 2018/8/20 上午6:19
  16.  */
  17. @Configuration
  18. public class ShiroConfig {
  19.     @Bean
  20.     public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
  21.         System.out.println("ShiroConfiguration.shirFilter()");
  22.         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  23.         shiroFilterFactoryBean.setSecurityManager(securityManager);
  24.         //拦截器.
  25.         Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
  26.         // 配置不会被拦截的链接 顺序判断
  27.         filterChainDefinitionMap.put("/css/**""anon");
  28.         filterChainDefinitionMap.put("/js/**""anon");
  29.         filterChainDefinitionMap.put("/img/**""anon");
  30.         filterChainDefinitionMap.put("/components/**""anon");
  31.         filterChainDefinitionMap.put("/favicon.ico""anon");
  32.         //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
  33.         filterChainDefinitionMap.put("/logout""logout");
  34.         //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
  35.         //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
  36.         filterChainDefinitionMap.put("/admin/**""authc");
  37.         filterChainDefinitionMap.put("/user/**""authc");
  38.         filterChainDefinitionMap.put("/**""anon");
  39.         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
  40.         // 如果不设置默认会自动寻找Web工程根目录下的"/login"页面
  41.         shiroFilterFactoryBean.setLoginUrl("/login");
  42.         // 登录成功后要跳转的链接
  43.         shiroFilterFactoryBean.setSuccessUrl("/");
  44.         //未授权界面;
  45.         shiroFilterFactoryBean.setUnauthorizedUrl("/403");
  46.         return shiroFilterFactoryBean;
  47.     }
  48.     /**
  49.      * 凭证匹配器
  50.      * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
  51.      * )
  52.      * @return
  53.      */
  54.     @Bean
  55.     public HashedCredentialsMatcher hashedCredentialsMatcher(){
  56.         HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
  57.         //散列算法:这里使用MD5算法;
  58.         hashedCredentialsMatcher.setHashAlgorithmName("md5");
  59.         //散列的次数,比如散列两次,相当于 md5(md5(""));
  60.         hashedCredentialsMatcher.setHashIterations(1);
  61.         return hashedCredentialsMatcher;
  62.     }
  63.     @Bean
  64.     public MyShiroRealm myShiroRealm(){
  65.         MyShiroRealm myShiroRealm = new MyShiroRealm();
  66.         myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
  67.         return myShiroRealm;
  68.     }
  69.     @Bean
  70.     public SecurityManager securityManager(){
  71.         DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
  72.         //设置realm
  73.         securityManager.setRealm(myShiroRealm());
  74.         return securityManager;
  75.     }
  76.     /**
  77.      *  开启shiro aop注解支持.
  78.      *  使用代理方式;所以需要开启代码支持;
  79.      * @param securityManager
  80.      * @return
  81.      */
  82.     @Bean
  83.     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
  84.         AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
  85.         authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
  86.         return authorizationAttributeSourceAdvisor;
  87.     }
  88.     @Bean(name="simpleMappingExceptionResolver")
  89.     public SimpleMappingExceptionResolver
  90.     createSimpleMappingExceptionResolver() {
  91.         SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
  92.         Properties mappings = new Properties();
  93.         //数据库异常处理
  94.         mappings.setProperty("DatabaseException""databaseError");
  95.         mappings.setProperty("UnauthorizedException","403");
  96.         r.setExceptionMappings(mappings);
  97.         r.setDefaultErrorView("error");
  98.         r.setExceptionAttribute("message");
  99.         return r;
  100.     }
  101. }

这里补充一下链接器链,是按顺序匹配的。必须使用LinkedHashMap,因为HashMap遍历是无序的。

目前我是放行除 /admin/** 和 /user/** 之外所有的页面,通常情况下是放行匿名的页面,其他的一律需要授权验证,如

filterChainDefinitionMap.put("/**""authc");

 

4、LoginParam.java

  1. package com.liuyanzhao.blog.web.param;
  2. import lombok.Data;
  3. import java.io.Serializable;
  4. /**
  5.  * 登录参数
  6.  * @author 言曌
  7.  * @date 2018/9/9 上午11:42
  8.  */
  9. @Data
  10. public class LoginParam implements Serializable{
  11.     private static final long serialVersionUID = 166457193110647497L;
  12.     private String username;
  13.     private String password;
  14.     private String captchaCode;
  15.     private boolean rememberMe;
  16.     private String CSRFToken;
  17. }

 

5、LoginController.java

  1. /**
  2.      * 登录页面
  3.      *
  4.      * @return
  5.      */
  6.     @GetMapping("/login")
  7.     public String loginPage() {
  8.         return "login";
  9.     }
  10.     /**
  11.      * 登录提交
  12.      *
  13.      * @param loginParam
  14.      * @param model
  15.      * @return
  16.      * @throws Exception
  17.      */
  18.     @PostMapping("/login")
  19.     public String login(LoginParam loginParam,
  20.                         Model model) throws Exception {
  21.         //1、验证用户名和密码
  22.         org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
  23.         UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginParam.getUsername(), loginParam.getPassword(), loginParam.isRememberMe());
  24.         String msg = "";
  25.         try {
  26.             subject.login(usernamePasswordToken);
  27.             return "redirect:/";
  28.         } catch (UnknownAccountException e) {
  29.             log.info("UnknownAccountException -- > 账号不存在:");
  30.             msg = "账号不存在!";
  31.         } catch (IncorrectCredentialsException e) {
  32.             log.info("IncorrectCredentialsException -- > 密码不正确:");
  33.             msg = "密码不正确!";
  34.         } catch (LockedAccountException e) {
  35.             log.info("LockedAccountException -- > 账号被锁定");
  36.             msg = "账号被锁定!";
  37.         } catch (Exception e) {
  38.             log.info(e.getMessage());
  39.         }
  40.         model.addAttribute("msg", msg);
  41.         return "login";
  42.     }

 

6、login.html

  1. <form name="loginform" id="loginform" action="/login" method="post">
  2.     <p>
  3.         <label for="username">用户名或电子邮件地址<br/>
  4.             <input type="text" name="username" id="username" class="input" size="20" required/>
  5.         </label>
  6.     </p>
  7.     <p>
  8.         <label for="password">密码<br/>
  9.             <input type="password" name="password" id="password" class="input" size="20" required/>
  10.         </label>
  11.     </p>
  12.     <p th:if="${msg}">
  13.         <label for="captchaCode">验证码<br/>
  14.             <input type="text" name="captchaCode" id="captchaCode" class="input" size="20"
  15.                    style="float:left;width: 40%; " required/>
  16.             <img src="/img/getKaptchaImage" alt="" style="float:left;padding-top: 3px;">
  17.             <span>换一张</span>
  18.         </label>
  19.     </p>
  20.     <input type="hidden" name="CSRFToken" th:value="${session.CSRFToken}">
  21.     <div style="clear: both;"></div>
  22.     <p class="forgetmenot">
  23.         <label for="rememberme">
  24.         <input name="rememberMe" type="checkbox" id="rememberMe"
  25.                checked="checked"> 记住我的登录信息</label>
  26.     </p>
  27.     <p class="submit">
  28.         <input type="submit" class="button button-primary button-large" value="登录"/>
  29.     </p>
  30.     <br>
  31. </form>

这里主要关注 form 表单提交的 username 和 password 即可,其中 CSRF 防护忽略,rememberMe 下面要用到。

 

三、添加 kaptcha 验证码

1、添加验证码依赖

  1. <!--验证码-->
  2. <dependency>
  3.     <groupId>com.github.penggle</groupId>
  4.     <artifactId>kaptcha</artifactId>
  5.     <version>2.3.2</version>
  6. </dependency>

 

2、验证码配置类

  1. package com.liuyanzhao.blog.web.config;
  2. import com.google.code.kaptcha.impl.DefaultKaptcha;
  3. import com.google.code.kaptcha.util.Config;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import java.util.Properties;
  7. /**
  8.  * 验证码图片样式配置
  9.  * @author 言曌
  10.  * @date 2018/9/2 上午10:23
  11.  */
  12. @Configuration
  13. public class KaptchaConfig {
  14.     @Bean(name="captchaProducer")
  15.     public DefaultKaptcha getKaptchaBean(){
  16.         DefaultKaptcha defaultKaptcha=new DefaultKaptcha();
  17.         Properties properties=new Properties();
  18.         //验证码字符范围
  19.         properties.setProperty("kaptcha.textproducer.char.string""23456789");
  20.         //图片边框颜色
  21.         properties.setProperty("kaptcha.border.color""245,248,249");
  22.         //字体颜色
  23.         properties.setProperty("kaptcha.textproducer.font.color""black");
  24.         //文字间隔
  25.         properties.setProperty("kaptcha.textproducer.char.space""1");
  26.         //图片宽度
  27.         properties.setProperty("kaptcha.image.width""100");
  28.         //图片高度
  29.         properties.setProperty("kaptcha.image.height""35");
  30.         //字体大小
  31.         properties.setProperty("kaptcha.textproducer.font.size""30");
  32.         //session的key
  33. //        properties.setProperty("kaptcha.session.key", "code");
  34.         //长度
  35.         properties.setProperty("kaptcha.textproducer.char.length""4");
  36.         //字体
  37.         properties.setProperty("kaptcha.textproducer.font.names""宋体,楷体,微软雅黑");
  38.         Config config=new Config(properties);
  39.         defaultKaptcha.setConfig(config);
  40.         return defaultKaptcha;
  41.     }
  42. }

 

3、验证码控制器

  1. package com.liuyanzhao.blog.web.controller.common;
  2. import com.google.code.kaptcha.Constants;
  3. import com.google.code.kaptcha.Producer;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Controller;
  7. import org.springframework.web.bind.annotation.GetMapping;
  8. import javax.imageio.ImageIO;
  9. import javax.servlet.ServletOutputStream;
  10. import java.awt.image.BufferedImage;
  11. /**
  12.  * 验证码控制器
  13.  * @author 言曌
  14.  * @date 2018/9/2 上午10:41
  15.  */
  16. @Controller
  17. @Slf4j
  18. public class KaptchaController extends BaseController {
  19.     @Autowired
  20.     private Producer captchaProducer;
  21.     @GetMapping("/img/getKaptchaImage")
  22.     public void getKaptchaImage() throws Exception {
  23.         response.setDateHeader("Expires"0);
  24.         // Set standard HTTP/1.1 no-cache headers.
  25.         response.setHeader("Cache-Control""no-store, no-cache, must-revalidate");
  26.         // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
  27.         response.addHeader("Cache-Control""post-check=0, pre-check=0");
  28.         // Set standard HTTP/1.0 no-cache header.
  29.         response.setHeader("Pragma""no-cache");
  30.         // return a jpeg
  31.         response.setContentType("image/jpeg");
  32.         // create the text for the image
  33.         String capText = captchaProducer.createText();
  34.         //将验证码存到session
  35.         session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);
  36.         log.info(capText);
  37.         // create the image with the text
  38.         BufferedImage bi = captchaProducer.createImage(capText);
  39.         ServletOutputStream out = response.getOutputStream();
  40.         // write the data out
  41.         ImageIO.write(bi, "jpg", out);
  42.         try {
  43.             out.flush();
  44.         } finally {
  45.             out.close();
  46.         }
  47.     }
  48. }

 

4、修改 LoginController.java

  1. /**
  2.  * 登录提交
  3.  *
  4.  * @param loginParam
  5.  * @param model
  6.  * @return
  7.  * @throws Exception
  8.  */
  9. @PostMapping("/login")
  10. public String login(LoginParam loginParam,
  11.                     Model model) throws Exception {
  12.     //1、检验验证码
  13.     if (loginParam.getCaptchaCode() != null) {
  14.         String inputCode = request.getParameter("captchaCode");
  15.         String captchaSession = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
  16.         if (!Objects.equals(inputCode, captchaSession)) {
  17.             log.info("验证码错误,用户输入:{}, 正确验证码:{}", inputCode, captchaSession);
  18.             model.addAttribute("msg""验证码不正确!");
  19.             CsrfTokenUtil.refreshToken(request);
  20.             return "login";
  21.         }
  22.     }
  23.     //2、验证用户名和密码
  24.     org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
  25.     UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginParam.getUsername(), loginParam.getPassword());
  26.     String msg = "";
  27.     try {
  28.         subject.login(usernamePasswordToken);
  29.         return "redirect:/";
  30.     } catch (UnknownAccountException e) {
  31.         log.info("UnknownAccountException -- > 账号不存在:");
  32.         msg = "账号不存在!";
  33.     } catch (IncorrectCredentialsException e) {
  34.         log.info("IncorrectCredentialsException -- > 密码不正确:");
  35.         msg = "密码不正确!";
  36.     } catch (LockedAccountException e) {
  37.         log.info("LockedAccountException -- > 账号被锁定");
  38.         msg = "账号被锁定!";
  39.     } catch (Exception e) {
  40.         log.info(e.getMessage());
  41.     }
  42.     model.addAttribute("msg", msg);
  43.     CsrfTokenUtil.refreshToken(request);
  44.     return "login";
  45. }

 

 

四、配置记住我

1、修改 ShiroConfig.java

  1. /**
  2.     * cookie对象;
  3.     * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cookie的有效时间等等。
  4.     * @return
  5.     */
  6.    @Bean
  7.    public SimpleCookie rememberMeCookie(){
  8.        //System.out.println("ShiroConfiguration.rememberMeCookie()");
  9.        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
  10.        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
  11.        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
  12.        simpleCookie.setMaxAge(259200);
  13.        return simpleCookie;
  14.    }
  15.    /**
  16.     * cookie管理对象;
  17.     * rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
  18.     * @return
  19.     */
  20.    @Bean
  21.    public CookieRememberMeManager rememberMeManager(){
  22.        //System.out.println("ShiroConfiguration.rememberMeManager()");
  23.        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
  24.        cookieRememberMeManager.setCookie(rememberMeCookie());
  25.        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
  26.        cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
  27.        return cookieRememberMeManager;
  28.    }
  29.    @Bean
  30.    public SecurityManager securityManager(){
  31.        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
  32.        //设置realm
  33.        securityManager.setRealm(myShiroRealm());
  34.        //用户授权/认证信息Cache, 采用EhC//注入记住我管理器
  35.        securityManager.setRememberMeManager(rememberMeManager());
  36.        return securityManager;
  37.    }

 

2、修改 LoginController.java

主要是修改

  1. UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginParam.getUsername(), loginParam.getPassword(), loginParam.isRememberMe());

最终如下

  1. /**
  2.  * 登录提交
  3.  *
  4.  * @param loginParam
  5.  * @param model
  6.  * @return
  7.  * @throws Exception
  8.  */
  9. @PostMapping("/login")
  10. public String login(LoginParam loginParam,
  11.                     Model model) throws Exception {
  12.     //1、检验验证码
  13.     if (loginParam.getCaptchaCode() != null) {
  14.         String inputCode = request.getParameter("captchaCode");
  15.         String captchaSession = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
  16.         if (!Objects.equals(inputCode, captchaSession)) {
  17.             log.info("验证码错误,用户输入:{}, 正确验证码:{}", inputCode, captchaSession);
  18.             model.addAttribute("msg""验证码不正确!");
  19.             CsrfTokenUtil.refreshToken(request);
  20.             return "login";
  21.         }
  22.     }
  23.     //2、验证用户名和密码
  24.     org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
  25.     UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginParam.getUsername(), loginParam.getPassword(), loginParam.isRememberMe());
  26.     String msg = "";
  27.     try {
  28.         subject.login(usernamePasswordToken);
  29.         return "redirect:/";
  30.     } catch (UnknownAccountException e) {
  31.         log.info("UnknownAccountException -- > 账号不存在:");
  32.         msg = "账号不存在!";
  33.     } catch (IncorrectCredentialsException e) {
  34.         log.info("IncorrectCredentialsException -- > 密码不正确:");
  35.         msg = "密码不正确!";
  36.     } catch (LockedAccountException e) {
  37.         log.info("LockedAccountException -- > 账号被锁定");
  38.         msg = "账号被锁定!";
  39.     } catch (Exception e) {
  40.         log.info(e.getMessage());
  41.     }
  42.     model.addAttribute("msg", msg);
  43.     CsrfTokenUtil.refreshToken(request);
  44.     return "login";
  45. }

 

3、login.html 添加记住我的复选框

name为之前填的 rememberMe

 

4、修改 ShiroConfig.java

上面的配置后,当登录后,会创建rememberMe的 cookie,退出浏览器,cookie依然存在。

但是我们访问需要登录(authc)的页面会被拦截到登录页面

解决办法是修改 authc 为 user

我们要修改

  1. filterChainDefinitionMap.put("/admin/**""authc");
  2. filterChainDefinitionMap.put("/user/**""authc");

  1. filterChainDefinitionMap.put("/admin/**""user");
  2. filterChainDefinitionMap.put("/user/**""user");

 

然后再次尝试,发现可以访问。

 

五、效果图如下

首次登录无需验证码,登录错误需要验证码

SpringBoot整合Shiro实现权限控制,验证码,记住我SpringBoot整合Shiro实现权限控制,验证码,记住我

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
言曌

发表评论

:?::razz::sad::evil::!::smile::oops::grin::eek::shock::???::cool::lol::mad::twisted::roll::wink::idea::arrow::neutral::cry::mrgreen: