Spring boot 整合shiro 实现登陆验证

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

<properties>        
        <shiro-version>1.2.5shiro-version>
        <extras-shiro-version>1.2.1extras-shiro-version>
properties>
<dependencies>
        
        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-springartifactId>
            <version>${shiro-version}version>
        dependency>
        
        
        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-ehcacheartifactId>
            <version>${shiro-version}version>
        dependency>
        
        <dependency>
            <groupId>com.github.theborakompanionigroupId>
            <artifactId>thymeleaf-extras-shiroartifactId>
            <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"); //配置退出
        // 
        // 
        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 roles = new ArrayList();  //角色
        List prems = new ArrayList(); //权限
        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


<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>
            
            <span id="basic-addon0"> 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。

你可能感兴趣的:(spring,cloud应用篇)