Spring boot 整合shiro 实现登陆验证

一、pom中引入shiro的依赖(省去多余代码)

<properties>        
        <shiro-version>1.2.5</shiro-version>
        <extras-shiro-version>1.2.1</extras-shiro-version>
</properties>
<dependencies>
        <!-- shiro spring -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro-version}</version>
        </dependency>
        <!-- shiro ehcache (shiro缓存)-->
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro-version}</version>
        </dependency>
        <!-- shiro thymeleaf-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>${extras-shiro-version}</version>
        </dependency>
</dependencies>

三个依赖组件,三种用途,已在注释中标注清楚。

二、shiro 配置 ShiroConfiguration,取代以往的 xml 文件,这里使用java 配置。

@Configuration
public class ShiroConfiguration {
    //...
}

1、配置过滤器 shiroFilter

    @Bean(name="shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        shiroFilterFactoryBean.setLoginUrl("/login");//登录连接
        shiroFilterFactoryBean.setSuccessUrl("/index");//登录成功后跳转的连接
        shiroFilterFactoryBean.setUnauthorizedUrl("/pages/403"); //未授权跳转页面

        //定义shiro过滤链
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //配置退出
        filterChainDefinitionMap.put("/logout", "logout"); //配置退出
        // <!-- 过滤链定义,从上向下顺序执行,/**放在最下面,过滤链的最后一关,表示除去以上各环节,剩余url的都需要验证 -->
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/js/**","anon");
        filterChainDefinitionMap.put("/css/**","anon");
        filterChainDefinitionMap.put("/register","anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

2、配置安全管理器 SecurityManager

    @Bean
    public SecurityManager securityManager(){
        //使用默认的安全管理器
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(securityRealm());  //加入自定义的安全领域
        return securityManager;
    }

    @Bean
    public SecurityRealm securityRealm(){
        SecurityRealm securityRealm = new SecurityRealm();
        securityRealm.setCredentialsMatcher(hashedCredentialsMatcher());//凭证匹配器
        securityRealm.setCachingEnabled(false);//不使用缓存
        return securityRealm;
    }

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher  hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//使用MD5散列算法
        hashedCredentialsMatcher.setHashIterations(1);//散列次数,这里等于1次MD5
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);  //散列后密码为16进制,要与生成密码时一致。false 表示Base64编码
        return hashedCredentialsMatcher;
    }

三、自定义安全领域 SecurityRealm ,继承shiro的抽象类 AuthorizingRealm,重写认证和授权两个方法。本节主要内容是登陆验证,我们只详细实现认证环节。

public class SecurityRealm extends AuthorizingRealm {
    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String currentUserName = (String)principalCollection.getPrimaryPrincipal();
        List<String> roles = new ArrayList<String>();  //角色
        List<String> prems = new ArrayList<String>(); //权限
        roles.add("baidu");
        roles.add("google");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRoles(roles);
        authorizationInfo.addStringPermissions(prems);
        return authorizationInfo;
    }

    /**
     * 认证,验证当前登录的Subject
     * LoginController.login 方法中调用
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

       SecurityUtils.getSubject().getSession().getId(),AuthenticationInfo.class);
       String userName = (String)authenticationToken.getPrincipal();
       //为了测试通过,这里暂时写死, user: admin password: 123456
       // UserVo user = loginClient.selectUserByName(userName);
       UserVo user = new UserVo();
       user.setName(userName);
       user.setPassword(new Md5Hash("123456").toHex()); //与SecurityManager加密方式一致,使用一次散列,并转为16进制,验证方能通过。
       if(user == null){
           throw new UnknownAccountException();//没找到帐号
       }
       //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                "admin", //用户名
                user.getPassword(), //密码
                getName()  //realm name
        );
        return authenticationInfo;
    }
}

四、控制器Controller

    @PostMapping("/login")
    public String login(Model model, @Valid UserVo userVo, BindingResult bindingResult, RedirectAttributes redirectAttributes){
        if(bindingResult.hasErrors()){
            model.addAttribute("error",bindingResult.getFieldError().getDefaultMessage());
            return "login";
        }
        String userName = userVo.getName();
        UsernamePasswordToken token = new UsernamePasswordToken(userVo.getName(), userVo.getPassword());
        Subject currentUser = SecurityUtils.getSubject();

        try {
            currentUser.login(token);
        }catch (IncorrectCredentialsException ice){
            logger.info("对用户【" + userName +"】进行登录验证,验证未通过,错误的凭证!");
            redirectAttributes.addFlashAttribute("error","用户名或密码不正确!");
        }catch(UnknownAccountException uae){
            logger.info("对用户【" + userName +"】进行登录验证,验证未通过,未知账户!");
            redirectAttributes.addFlashAttribute("error","未知账户!");
        }catch(LockedAccountException lae){
            logger.info("对用户【" + userName +"】进行登录验证,验证未通过,账户锁定!");
            redirectAttributes.addFlashAttribute("error","账户已锁定!");
        }catch(ExcessiveAttemptsException eae){
            logger.info("对用户【" + userName +"】进行登录验证,验证未通过,错误次数太多!");
            redirectAttributes.addFlashAttribute("error","用户名或密码错误次数太多!");
        }catch(AuthenticationException ae){
            logger.info("对用户【" + userName +"】进行登录验证,验证未通过,堆栈轨迹如下:!");
            ae.printStackTrace();
            redirectAttributes.addFlashAttribute("error","用户名或密码不正确!");
        }

        if(currentUser.isAuthenticated()){
            model.addAttribute("name",userName);
            return "index";
        }else{
            token.clear();
            return "redirect:/login";
        }
    }

五、前端页面,任意写一个登陆表单即可验证我这里使用 thymeleaf

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
    <title>登录</title>
    <style>
        body{
            margin-left:auto;
            margin-right:auto;
            margin-TOP:100PX;
            width:20em;
        }
    </style>
</head>
<body>
    <form th:action="@{/login}" method="post">
        <div>
            <!--/*@thymesVar id="error" type=""*/-->
            <span id="basic-addon0">&nbsp;</span>
            <span style="font-size: 12px;color: red" th:text="${error}" aria-describedby="basic-addon0"></span>
        <br />
        </div>
        <div>
            <span id="basic-addon1">@</span>
            <input id="user_name" name="name" type="text" placeholder="用户名" aria-describedby="basic-addon1" />

        </div>
        <br />
        <div>
            <span id="basic-addon2">@</span>
            <input id="password" name="password" type="password" placeholder="密码" aria-describedby="basic-addon2" />
        </div>
        <br />
        <button type="submit" style="width:190px;">登 录</button>
    </form>
</body>
</html>

六、封装接收请求参数的UserVo 实体类。

public class UserVo {

    @NotEmpty(message="用户名不能为空!")
    private String name;
    @Size(min=6,max=10,message = "密码长度必须6到10位")
    private String password;

    //...省去getter setter 方法
}

github源码:Clone with HTTPS:https://github.com/libinbin8130/personal-websites.git

说明:本章节内容所在项目路径(pw-platform/pf-front/pf-web),由于是多模块项目,pf-web的构建依赖于跟目录pf-front的pom。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值