• 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.   `password` varchar(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(2);
    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.     <th:if="${msg}">
    13.         <label for="captchaCode">验证码<br/>
    14.             <input type="text" name="captchaCode" id="captchaCode" class="input" size="20"
    15.                    style="float:left; 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.     <class="forgetmenot">
    23.         <label for="rememberme">
    24.         <input name="rememberMe" type="checkbox" id="rememberMe"
    25.                checked="checked"> 记住我的登录信息</label>
    26.     </p>
    27.     <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");

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

    五、效果图如下

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

  • 相关阅读:
    Spring Boot学习笔记
    打造高效率的软件测试
    如何将测试结果在jenkins上发布
    如何在docker container中运行web自动化测试
    Jmeter中随机读取测试文件的内容
    如何提高UI自动化测试的质量
    mac系统上添加定时任务
    keypass口令管理实践
    GPG实践
    树的遍历
  • 原文地址:https://www.cnblogs.com/telwanggs/p/10809591.html
Copyright © 2020-2023  润新知