美文网首页
基于springboot接入shiro简单入门

基于springboot接入shiro简单入门

作者: Martain | 来源:发表于2020-10-13 11:26 被阅读0次

    前言

    前几天项目中要接入shiro,查看了它的官方教程以及示例代码,可以说已经是非常详细了。但是对于我这种之前没有接触过,马上要上手的情况来说,欠缺了如何在springboot项目中如何接入的教程,网上也有很多很好的博客写了springboot如何接入shiro,毕竟shiro并不是什么新东西,但是我看了许多博客都是配置的比较全的功能的,shiro是一个轻量级的框架,可以自定义添加需要哪些功能。一下子上来太多配置让我有点懵,所以我踩了一下坑之后,记录下了一个比较简单、轻量的接入文章,该项目只是简单的实现了认证功能,鉴权功能并没有深入。

    依赖

    shiro在maven库中的包有许多个,比如shiro-coreshiro-webshiro-spring等,因为我是基于springboot来接入shiro,所以我这里添加的是shiro-spring-boot-web-starter 依赖。

    <!-- springboot starter 依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency> 
    
    <!-- shiro 依赖 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring-boot-web-starter</artifactId>
      <version>1.5.3</version>
    </dependency>
    

    基础

    shiro需要接入用户信息,所以这里写了个简单的service来实现用户信息的查询。

    model

    package com.martain.shirodemo.model;
    
    public class User {
    
        private String userName;
    
        private String password;
    
        public User(String userName, String password) {
            this.userName = userName;
            this.password = password;
        }
    
        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;
        }
    }
    
    

    Service

    package com.martain.shirodemo.service;
    
    import com.martain.shirodemo.model.User;
    
    /**
     * 用户服务 接口
     * @author martain
     */
    public interface IUserService {
    
        /**
         * 通过用户名来
         * @param userName
         * @return
         */
        User findByUserName(String userName);
    }
    
    package com.martain.shirodemo.service.impl;
    
    import com.martain.shirodemo.model.User;
    import com.martain.shirodemo.service.IUserService;
    import org.springframework.stereotype.Service;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 用户服务实现
     * @author martain
     */
    @Service
    public class UserServiceImpl implements IUserService {
    
        /**
         * 模拟静态数据库
         */
        private final static Map<String,User> users = new HashMap<>();
        static {
            users.put("haitao",new User("haitao","123456"));
            users.put("martain",new User("martain","1024"));
        }
    
        /**
         * 通过用户名查询用户
         * @param userName 用户名
         * @return 用户记录
         */
        @Override
        public User findByUserName(String userName) {
            User user = users.get(userName);
            return user;
        }
    }
    

    接入shiro

    shiro 是非常好用的一个框架,使用他只需要自己添加一些配置即可,如果需要许多的自定义功能只需要简单的重写一些方法或类即可。为了更快的接入,我这里使用了尽可能的少的配置来启动shiro。简单的应用中,shiro的认证原理有如下几个步骤:

    1. 应用代码通过SecurityUtils.getSubject()来获取到subject
    2. subject调用一些方法(比如login等)来委托SecurityManager来进行认证和授权。
    3. SecurityManager通过注入的Realm来获取用户信息来实现认证和授权

    自定义Realm

    Shiro 认证相关的功能都有SecurityManager 委托给了Realm来实现认证的功能。也可以称他为域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

    package com.martain.shirodemo.config;
    
    import com.martain.shirodemo.model.User;
    import com.martain.shirodemo.service.IUserService;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.springframework.beans.factory.annotation.Autowired;
    
    /**
     * 自定义Realm
     * @author martain
     */
    public class CustomUserRealm extends AuthorizingRealm {
    
        @Autowired
        IUserService userService;
    
        /**
         * 鉴权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("doGetAuthorizationInfo...");
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            return simpleAuthorizationInfo;
        }
    
        /**
         * 认证
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException        {
            System.out.println("doGetAuthenticationInfo...");
            // 因为我们在后面调用Subject.login时传入的是UsernamePasswordToken类型 所以 AuthenticationToken 是 UsernamePasswordToken 类型
            UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
            String username = usernamePasswordToken.getUsername();
            String password = String.valueOf(usernamePasswordToken.getPassword());
            User user = userService.findByUserName(username);
            if (user == null){
                throw new AuthenticationException("用户不存在");
            }
            // 这里只是做了简单的比对
            if (!user.getPassword().equalsIgnoreCase(password))
            {
                throw new AuthenticationException("用户密码错误");
            }
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
            return simpleAuthenticationInfo;
        }
    }
    

    编写ShiroConfig

    Shiro配置文件中可以配置许多的东西,但不是都是必须的,所以我这里只是添加了必要的配置:注入Realm、注入SecurityManager以及shiroFilterFactoryBean,当然,如果这些没有配置的话程序也是无法启动的。

    package com.martain.shirodemo.config;
    import com.sun.org.apache.xerces.internal.util.SecurityManager;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    @Configuration
    public class ShiroConfig {
    
        /**
         * 注入Realm 
         * @return
         */
        @Bean
        public CustomUserRealm myUserRealm() {
            CustomUserRealm customUserRealm = new CustomUserRealm();
            return customUserRealm;
        }
    
        /**
         * 注入SecurityManager
         *
         * @return
         */
        @Bean
        public DefaultWebSecurityManager securityManager() {
            DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
            manager.setRealm(myUserRealm());
            return manager;
        }
    
        /**
         * Filter 工厂
         * -设置过滤条件以及跳转条件
         *
         * @param securityManager
         * @return
         */
        @Bean("shiroFilterFactoryBean")
        public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            bean.setSecurityManager(securityManager);
            /**
             * anon:匿名用户可访问
             * authc:认证用户可访问ShiroFilterFactoryBean
             * user:使用rememberMe可访问
             * perms:对应权限可访问
             * role:对应角色权限可访问
             **/
            Map<String, String> filterChainMap = new LinkedHashMap<>();
            // 登录接口开放
            filterChainMap.put("/login", "anon");
            // 获取用户信息需要认证用户
            filterChainMap.put("/user/**", "authc");
    
            // 未授权时的跳转url,比如跳转到登录页面
            bean.setLoginUrl("/login");
            // 首页
            bean.setSuccessUrl("/index");
            // 错误页面
            bean.setUnauthorizedUrl("/error");
    
            bean.setFilterChainDefinitionMap(filterChainMap);
            return bean;
        }
    }
    
    

    测试接口

    package com.martain.shirodemo.controller;
    
    import com.martain.shirodemo.service.IUserService;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.AuthorizationException;
    import org.apache.shiro.subject.Subject;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author martain
     */
    @RestController
    public class AuthController { 
    
        @GetMapping("/login")
        public String login(String userName,String password){
            /**
             * 这里的注入的Token类型 就是 出入Realm中doGetAuthenticationInfo的参数类型
             */
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, password);
            Subject subject = SecurityUtils.getSubject();
            try {
                //进行验证,这里可以捕获异常,然后返回对应信息
                subject.login(usernamePasswordToken);
                // 检测角色
                //  subject.checkRole("admin");
                // 检测权限
                //  subject.checkPermissions("query", "add");
            } catch (UnknownAccountException e) {
                return "用户名不存在!";
            } catch (AuthenticationException e) {
                System.out.println(e.getLocalizedMessage());
                return "账号或密码错误!";
            } catch (AuthorizationException e) {
                return "没有权限";
            }
    
            return "Hello,"+userName;
        }
    
        @GetMapping("/user/{token}")
        public String getUserInfo(@PathVariable String token){
            return "Hello,your token is "+token;
        }
    
    }
    

    这里添加了两个测试接口,在前面ShiroConfig中已经对/login接口进行了权限的开放以及对 /user接口配置为需要认证。测试过程中,如果直接访问 /user接口是会跳转到 /login接口的,只有在/login接口访问成功之后,才可以成功访问 /user接口。

    这个只是按照Shiro的许多默认配置实现的有状态的认证,在实际使用过程中可以使用JWT配合Shiro来实现无状态的认证。

    相关文章

      网友评论

          本文标题:基于springboot接入shiro简单入门

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