美文网首页spring bootshiro
Springboot整合Shiro:简洁的身份认证

Springboot整合Shiro:简洁的身份认证

作者: 张小黑的猫 | 来源:发表于2019-05-23 15:33 被阅读507次
    shiro.gif
    *******完整代码在文章最下面,转载请说明出处,谢谢 *******

    简单的web应用进行身份认证的流程:
    1.对未认证的用户请求进行拦截,跳转到认证页面。
    2.用户通过用户名+密码及其他凭证进行身份认证,认证成功跳转成功页面,认证失败提示相关失败信息。

    根据流程,采用shiro进行快速开发。
    1.对未认证的用户请求进行拦截,跳转到认证页面。
    (0) 这里需要shiro的拦截器配置,新建ShiroConfig配置类,配置过滤器

    /**
     * @Description springboot中的Shiro配置类
     * @Author 张小黑的猫
     * @data 2019-05-22 17:17
     */
    @Configuration
    public class ShiroConfig {
        
        /**
         * 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
         * @return
         */
        @Bean
        public ShiroFilterFactoryBean webFilter(){
    
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    
            //配置拦截链 使用LinkedHashMap,因为LinkedHashMap是有序的,shiro会根据添加的顺序进行拦截
            // Map<K,V> K指的是拦截的url V值的是该url是否拦截
            Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);
    
            //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问,先配置anon再配置authc。
            filterChainMap.put("/login","anon");
            filterChainMap.put("/**", "authc");
    
            //设置拦截请求后跳转的URL.
            shiroFilterFactoryBean.setLoginUrl("/login");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
            return shiroFilterFactoryBean;
        }
    }
    
    1. Shiro的用户认证
      先说下shiro认证的流程:
      创建SecurityManager安全管理器 > 主体Subject提交认证信息 > SecurityManager安全管理器认证 > SecurityManager调用Authenticator认证器认证 >Realm验证
      有几个概念:
    • Subject:主体,代表了当前“用户”;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
    • SecurityManager安全管理器:所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;负责与后边介绍的其他组件进行交互。(类似于SpringMVC中的DispatcherServlet控制器)
    • Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

    (1)首先在ShiroConfig配置类中创建SecurityManager安全管理器

    public SecurityManager securityManager(){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            return securityManager;
        }
    

    这里值得注意的一点是SecurityManager导包的时候选org.apache.shiro.mgt.SecurityManager;
    (2)SecurityManager安全管理器需要到realm中去验证认证信息,所以给SecurityManager设置Realm
    Shiro的Realm分为IniRealmJdbcRealm以及自定义realm,我们这里使用自定义的realm实现业务逻辑。新建自定义Realm类CustomRealm继承AuthorizingRealm

    public class CustomRealm extends AuthorizingRealm {
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            return null;
        }
    }
    

    (3)重写AuthorizingRealm中的认证方法doGetAuthenticationInfo

    @Override
        /**
         * 认证
         */
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //1.获取用户输入的账号
            String username = (String)token.getPrincipal();
            //2.通过username从数据库中查找到user实体
             User user = getUserByUserName(username);
            if(user == null){
                return null;
            }
            //3.通过SimpleAuthenticationInfo做身份处理
            SimpleAuthenticationInfo simpleAuthenticationInfo =
                    new SimpleAuthenticationInfo(user,user.getPassword(),getName());
            //4.用户账号状态验证等其他业务操作
            if(!user.getAvailable()){
                throw new AuthenticationException("该账号已经被禁用");
            }
            //5.返回身份处理对象
            return simpleAuthenticationInfo;
        }
    

    上面的token凭证是来自主体Subject.login(token)提交时的token,主体的提交下面会说到。
    这里还有个比较有意思的点:new SimpleAuthenticationInfo(user,user.getPassword(),getName());中的参数问题。第一个参数是从数据库中获取的User对象,第二个参数是数据库获取的密码,第三个参数是当前Realm的名称。其中第一个参数传username也可以,那么传usernameUser对象的区别是啥呢?Shiro为我们提供了获取当前用户信息的方法:

         * shiro获取当前用户
         * @return
         */
        private User currentUser(){
            User currentUser = (User) SecurityUtils.getSubject().getPrincipal();
            return  currentUser;
        }
    

    有些业务场景需要获取当前用户的信息(用户的状态等等)进行业务操作,那么上面的区别就显而易见了。如果传的是username那么用户的其他信息就拿不到了。
    (4) 将自定义Realm注入到SecurityManager安全管理器中

    public SecurityManager securityManager(){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            //将自定义的realm交给SecurityManager管理
            securityManager.setRealm(new CustomRealm());
            return securityManager;
        }
    

    (5) Shiro配置类的过滤器中启用安全管理器,即shiroFilterFactoryBean中配置SecurityManager

    //设置securityManager
            shiroFilterFactoryBean.setSecurityManager(securityManager());
    

    (6) 主体提交认证信息,即登录请求

     @PostMapping("/login")
        public String login(String username, String password, Model model){
    
            UsernamePasswordToken token = new UsernamePasswordToken(username,password);
            Subject currentUser = SecurityUtils.getSubject();
    
            try {
                //主体提交登录请求到SecurityManager
                currentUser.login(token);
            }catch (IncorrectCredentialsException ice){
                model.addAttribute("msg","密码不正确");
            }catch(UnknownAccountException uae){
                model.addAttribute("msg","账号不存在");
            }catch(AuthenticationException ae){
                model.addAttribute("msg","状态不正常");
            }
            if(currentUser.isAuthenticated()){
                System.out.println("认证成功");
                model.addAttribute("currentUser",currentUser());
                return "success";
            }else{
                token.clear();
                return "login";
            }
        }
    

    到这基本认证的流程就已经通了。在这里贴一下项目结构和全部代码,供大家参考:


    image.png

    pom.xml:

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!-- shiro相关依赖 -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.3.2</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    

    ShiroConfig.java:

    /**
     * @Description springboot中的Shiro配置类
     * @Author 张小黑的猫
     * @data 2019-05-22 17:17
     */
    @Configuration
    public class ShiroConfig {
    
        /**
         * 配置Shiro核心 安全管理器 SecurityManager
         * SecurityManager安全管理器:所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;负责与后边介绍的其他组件进行交互。(类似于SpringMVC中的DispatcherServlet控制器)
         */
        public SecurityManager securityManager(){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            //将自定义的realm交给SecurityManager管理
            securityManager.setRealm(new CustomRealm());
            return securityManager;
        }
    
    
        /**
         * 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
         * @return
         */
        @Bean
        public ShiroFilterFactoryBean webFilter(){
    
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            //设置securityManager
            shiroFilterFactoryBean.setSecurityManager(securityManager());
    
            //配置拦截链 使用LinkedHashMap,因为LinkedHashMap是有序的,shiro会根据添加的顺序进行拦截
            // Map<K,V> K指的是拦截的url V值的是该url是否拦截
            Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);
            //配置退出过滤器logout,由shiro实现
            filterChainMap.put("/logout","logout");
            //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问,先配置anon再配置authc。
            filterChainMap.put("/login","anon");
            filterChainMap.put("/**", "authc");
    
            //设置默认登录的URL.
            shiroFilterFactoryBean.setLoginUrl("/login");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
            return shiroFilterFactoryBean;
        }
    }
    

    CustomRealm.java:

    /**
     * @Description: shiro的自定义realm
     * Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
     * @Author 张小黑的猫
     * @data 2019-05-22 17:51
     */
    public class CustomRealm extends AuthorizingRealm {
        @Override
        /**
         * 认证
         */
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //1.获取用户输入的账号
            String username = (String)token.getPrincipal();
            //2.通过username从数据库中查找到user实体
             User user = getUserByUserName(username);
            if(user == null){
                return null;
            }
            //3.通过SimpleAuthenticationInfo做身份处理
            SimpleAuthenticationInfo simpleAuthenticationInfo =
                    new SimpleAuthenticationInfo(user,user.getPassword(),getName());
            //4.用户账号状态验证等其他业务操作
            if(!user.getAvailable()){
                throw new AuthenticationException("该账号已经被禁用");
            }
            //5.返回身份处理对象
            return simpleAuthenticationInfo;
        }
    
        @Override
        /**
         * 授权
         */
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
            return null;
        }
    
        /**
         * 模拟通过username从数据库中查找到user实体
         * @param username
         * @return
         */
        private User getUserByUserName(String username){
            List<User> users = getUsers();
            for(User user : users){
                if(user.getUsername().equals(username)){
                    return user;
                }
            }
            return null;
        }
    
        /**
         * 模拟数据库数据
         * @return
         */
        private List<User> getUsers(){
            List<User> users = new ArrayList<>();
            users.add(new User("张小黑的猫","123qwe",true));
            users.add(new User("张小黑的狗","123qwe",false));
            return users;
        }
    
    }
    

    User.java

    /**
     * @Description 用户
     * @Author 张小黑的猫
     * @data 2019-05-22 19:18
     */
    public class User {
        private String username;
        private String password;
        private Boolean available;
    
        public User(String username, String password, Boolean available) {
            this.username = username;
            this.password = password;
            this.available = available;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public Boolean getAvailable() {
            return available;
        }
    
        public void setAvailable(Boolean available) {
            this.available = available;
        }
    }
    

    LoginController.java:

    /**
     * @Description 登录
     * @Author 张小黑的猫
     * @data 2019-05-22 18:17
     */
    @Controller
    public class LoginController {
    
        @GetMapping("/login")
        public String login(){
            return "login";
        }
    
    
        @PostMapping("/login")
        public String login(String username, String password, Model model){
    
            UsernamePasswordToken token = new UsernamePasswordToken(username,password);
            Subject currentUser = SecurityUtils.getSubject();
    
            try {
                //主体提交登录请求到SecurityManager
                currentUser.login(token);
            }catch (IncorrectCredentialsException ice){
                model.addAttribute("msg","密码不正确");
            }catch(UnknownAccountException uae){
                model.addAttribute("msg","账号不存在");
            }catch(AuthenticationException ae){
                model.addAttribute("msg","状态不正常");
            }
            if(currentUser.isAuthenticated()){
                System.out.println("认证成功");
                model.addAttribute("username",username);
                return "success";
            }else{
                token.clear();
                return "login";
            }
        }
    }
    

    login.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Login</title>
    </head>
    <body>
    <form action="/login" method="post">
        <span th:text="${msg}" style="color: red"></span><br>
        用户名:<input type="text" name="username"><br>
        密&emsp;码:<input type="password" name="password"><br>
        <input type="submit" value="Login">
    </form>
    </body>
    </html>
    

    success.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>success</title>
    </head>
    <body>
    <span th:text="'欢迎你,'+${username}"></span>
    </body>
    </html>
    

    共同学习,欢迎指正修改~ 喵喵喵❤
    下一篇文章:Springboot整合Shiro: 详细的权限管理

    相关文章

      网友评论

        本文标题:Springboot整合Shiro:简洁的身份认证

        本文链接:https://www.haomeiwen.com/subject/coqvzqtx.html