springboot集成shiro实现用户认证和权限验证案例

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/hyly_zhard/article/details/100087013

搭建流程:
1、首先创建表,使用shiro制作权限验证至少要又用户表、角色表、角色权限表、菜单地址表。
表结构图式例:
user:
在这里插入图片描述

role:
在这里插入图片描述

menu:
在这里插入图片描述
permission:
在这里插入图片描述

2、pom文件依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.syzw.shiro.test</groupId>
    <artifactId>shiro_demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shiro_demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <!--去掉默认日志,加载别的日志 , 切换log4j2日志读取  -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--log4j的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
        </dependency>


        <!--
            mybatis-plus-boot-starter里面包含了mybatis和spring进行整合的依赖,
          所以不需要导入mabatis-spring的依赖了直接就可以使用了。
        -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>

        <!--使用注解的方式给实体类添加set,get等方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>

        <!--shiro的核心依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--shiro和spring整合的依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!--jwt需要的依赖-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.xmlunit</groupId>
            <artifactId>xmlunit-core</artifactId>
        </dependency>




    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


3、自定义Realm。


/**
 * @program: shiro_demo
 * @description:
 *      自定义的Realm数据源,用于给安全管理器做鉴权认证使用,realm在主体对象Subject调用login进行认证的时候,
 *  Subject将认证委托给安全管理器SecurityManager,SecurityManager最终还是会到达这个地方,执行这里的认证逻辑的。
 * @author: hyly
 * @create: 2019-08- 19:46
 */
public class MyRealm extends AuthorizingRealm  {
    //操作用户
    @Autowired
    private UserMapper userMapper;

    //操作角色表
    @Autowired
    private RoleMapper roleMapper;

    //操作权限表
    @Autowired
    private PermissionMapper permissionMapper;

    //具体的菜单权限
    @Autowired
    private MenuMapper menuMapper;

    public MyRealm(){
        //因为数据库中的密码做了散列,所以使用shiro的散列Matcher,如果数据库中没有使用md5加密的话,加上这个地方会报错
//        this.setCredentialsMatcher(new HashedCredentialsMatcher(Md5Hash.ALGORITHM_NAME));
    }
    //

    /**
     * 执行授权业务逻辑,主要用于做权限认证,也就是那些可以访问,那些不能访问
     * 这个地方就是用来给登录的角色进行赋权的,也就是说从这个地方获取登录的用户有那些权限,并且把这些权限告诉shiro,
     * 然后shiro就可以和拦截器中的拦截规则去验证,你是否有访问权限了。
     *
     * 这个地方需要访问数据库,获取当前用的权限,添加到shiro中。
     *
     * 这个方法之后登录成功之后才会访问
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //创建简单的授权信息
        SimpleAuthorizationInfo simpleAuthenticationInfo=new SimpleAuthorizationInfo();
        String userName= (String) principals.getPrimaryPrincipal();
        System.out.println(userName);

        //授权
        if(userName!=null){
            QueryWrapper<User> userWrapper=new QueryWrapper<>();
            userWrapper.eq("name",userName);
            User user=userMapper.selectOne(userWrapper);
            if(user!=null){
                System.out.println(userName);
                //查询当前登录用户的所有所有角色
                QueryWrapper<Role> wrapper=new QueryWrapper<>();
                wrapper.eq("id",user.getRole());
                List<Role> roleList=roleMapper.selectList(wrapper);

                //将遍历list,将list中role的的角色名取出,并且放到set集合中。
                Set<String> roles=roleList.stream().map(Role::getName).collect(Collectors.toSet());

                //将所有的角色信息添加进授权信息中
                simpleAuthenticationInfo.setRoles(roles);

                //查询出来角色对应的权限id
                QueryWrapper<Permission> permissionWrapper=new QueryWrapper<>();
                permissionWrapper.eq("id",user.getRole());
                List<Permission> permissionList=permissionMapper.selectList(permissionWrapper);
                //所有的menu的id,也就是具体菜单的id
                Set<String> menuIds=permissionList.stream().map(Permission::getMenu).collect(Collectors.toSet());


                if(menuIds.size()>0){
                    //根据权限id查询权限路径
                    QueryWrapper<Menu> menuWrapper=new QueryWrapper<>();
                    menuWrapper.in("id",menuIds);
                    List<Menu> menuUrls=menuMapper.selectList(menuWrapper);

                    //所有的菜单的url,也就是访问权限
                    Set<String> permission=menuUrls.stream().map(Menu::getUrl).collect(Collectors.toSet());

                    simpleAuthenticationInfo.setStringPermissions(permission);
                    //添加用户所对应的角色的所有权限
                }

                return simpleAuthenticationInfo;
            }
        }


        return null;
    }

    /**
     * 执行用户认证逻辑,也就是用户的登录,当subject主体对象调用loggin方法时,这个方法会被调用
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户传递过来的用户名和密码
        String userName= (String) token.getPrincipal();
        //由于shiro为了解决stirng不可变的特点,防止内存的可读性,因此将密码转成了char,所以也就有了shiro的加盐。
        String password=new String((char[])token.getCredentials());

        //查询数据库中账户
        QueryWrapper<User> wrapper=new QueryWrapper<User>();
        wrapper.eq("name",userName);
        User user =userMapper.selectOne(wrapper);
        if(user==null){
            return null;
        }

        //创建这个SimpleAuthenticationInfo对象就是为了将用户名和密码转成token,将这些敏感信息交由shiro管理,
//        return new SimpleAuthenticationInfo(user.getName(),user.getPassword(), ByteSource.Util.bytes(user.getSalt),getName());
        //SimpleAuthenticationInfo这个是认证信息
        //将数据库中的用户名和密码传递进去,shiro回去做匹配认证

//        byte[] decode = Base64.getDecoder().decode(user.getSalt());
//        ByteSource bytes = ByteSource.Util.bytes(decode);
        return new SimpleAuthenticationInfo(user.getName(),user.getPassword(),ByteSource.Util.bytes(user.getSalt()),getName());
    }


//    //设置这个Realm支持的验证token策略
//    @Override
//    public boolean supports(AuthenticationToken token) {
//        return super.supports(token);
//
////        例如,以下的只能支持JWTToken
////        return token instanceof JWTToken;
//    }
}

4、配置shiro的配置文件。


/**
 * @program: shiro_demo
 * @description: 用于配置Shiro的安全管理器,Realm源以及一些shiro拦截器
 * @author: hyly
 * @create: 2019-08- 19:44
 */
@Configuration
public class ShiroConfig {

    /**
     *  设置shiro权限拦截器
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        //创建shiro拦截器工厂,就像spring-mvc的拦截器链一样,ShiroFilterFactoryBean这个对象就像过滤器链
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

        //和jwt的token拦截验证绑定起来
        Map<String, Filter> filter=new HashMap<>();
        filter.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filter);


        /**
         * shiro内置过滤器:
         *      anon:无需认证(登录),可以直接访问。
         *      authc:必须进行认证才能访问。
         *      user:如果使用rememberMe的功能,可以直接访问呢。
         *      perms:该资源必须得到资源的权限才可以进行访问。
         *      role:该资源必须得到角色的权限才可以进行访问。
         *
         *      * URL 匹配风格
         *      * 1). ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/;
         *      * 2). *:匹配零个或多个字符串,如 /admin* 将匹配 /admin 或/admin123,但不匹配 /admin/1;
         *      * 2). **:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/b
         *      *
         *      * 配置身份验证成功,失败的跳转路径
         *
         */
        //获取拦截规则
        Map<String,String> filterMap=NoVerify.getNoerifyList();
        //当用户没有登录时跳转的界面
        shiroFilterFactoryBean.setLoginUrl("/user/login2");
        //设置用户没有权限的默认跳转界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuthen");
        //登录成功的路径
        shiroFilterFactoryBean.setSuccessUrl("/index");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        return shiroFilterFactoryBean;
    }

    /**
     * 创建出来安全管理器,用于进行鉴权使用
     * @param realm
     * @return
     */
    @Bean("securityManager")
    public DefaultWebSecurityManager getDefaultSecurityManager(@Qualifier("myRealm")MyRealm realm){
        //创建出来一个安全管理器
        DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
        //让创建出来的安全管理器使用自己自定义的realm源进行认证
        defaultWebSecurityManager.setRealm(realm);

        return defaultWebSecurityManager;

    }


    /**
     *创建自定义的realm源,供安全管理器使用进行鉴权,可以提供多个
     * @return
     */
    @Bean("myRealm")
    public MyRealm createMyRealm(){
        MyRealm myRealm=new MyRealm();
        //认证匹配器,用于给前端明文密码加密,和数据库取出的密文密码做检验
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myRealm;
    }





    /**
     * 配置Shiro生命周期处理器,让shiro自动加载很多默认的配置
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    /**
     * 自动创建代理类,若不添加,Shiro的注解可能不会生效。
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new
                DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启Shiro的注解
     *
     * 这个地方的@Qualifier("securityManager") DefaultWebSecurityManager securityManager可能会有问题,这个是我自己加的
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new
                AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
     * 凭证匹配器
     *
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 )
     * 因为数据库的密码加密了,而前端的输入的密码是明文密码,这样子去和数据库取出的密码做比较显然是不配的,
     * 因此就要设置加密规则,shiro在密码比较的时候,就会将前端的明文密码加密之后再进行对比。
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new
                HashedCredentialsMatcher();
        //设置加密规则
        hashedCredentialsMatcher.setHashAlgorithmName(Md5Hash.ALGORITHM_NAME);
        //设置系统凭证的编码转换格式,也就是密码转成什么格式去比对,默认为true,表示16进制,
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);
        //设置散列次数
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    /**
     * Shiro方言,支持Thymeleaf中使用shiro标签,如果在前端要使用这个shiro的标签,则需要导入Thymeleaf依赖
     */
//    @Bean
//    public ShiroDialect shiroDialect() {
//        return new ShiroDialect();
//    }

}

5、controller模拟跳转页面。


/**
 * @program: shiro_demo
 * @description: 用户模块
 * @author: hyly
 * @create: 2019-08- 21:14
 */
@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("/login")
    @ResponseBody
    public String login(String userName, String password){

        //将前端的用户名和密码存放到shiro中,这里不需要加密,因为realm源中设置了CredentialsMatcher
        //CredentialsMatcher认证匹配器中设置了加密规则
        UsernamePasswordToken token=new UsernamePasswordToken();
        token.setPassword(password.toCharArray());
        token.setUsername(userName);
		/**
			这里的账号密码到时候会自动使用认证器中的盐给这个明文加盐,然后和数据库查询出来的密文做匹配。
		*/

        //获取用户主体对象
        Subject subject= SecurityUtils.getSubject();

        try{
            //进行认证
          subject.login(token);
        }catch (Exception exception){
            exception.printStackTrace();
            return "登录失败,用户名或者密码不对";
        }
        //判断是否登录
        if(subject.isAuthenticated()){
            return "登录成功";
        }else{
            return "登录失败";
        }
    }

    /**
     * 用户登出
     */
    @GetMapping("/logout")
    public String logout(){
        Subject subject= SecurityUtils.getSubject();
        subject.logout();

        return"用户已经登出";
    }

    /**
     * 添加用户
     * @param user
     * @return
     */
    @PostMapping("/add")
    public Integer add(@RequestBody User user){
        Random random=new Random();
        String salt=random.nextInt()+"";

        Md5Hash md5Hash2=new Md5Hash(user.getPassword());
        System.out.println(md5Hash2.toBase64());

        Md5Hash md5Hash=new Md5Hash(user.getPassword(),salt);
        user.setSalt(salt);
        user.setPassword(md5Hash.toBase64());
        Integer reuslt = userMapper.insert(user);

        return reuslt;
    }

    /**
     * 获取用户列表
     * @return
     */
    @GetMapping("/getList")
    public List<User> getList(){
        QueryWrapper<User> wrapper=new QueryWrapper<>();
        List<User> list=userMapper.selectList(wrapper);
        return list;
    }

    /**
     * 模拟错误错误页面
     * @return
     */
    @GetMapping("/error")
    public String error(){
        return "500错误页面";
    }

    /**
     * 模拟用户没有权限页面
     * @return
     */
    @GetMapping("/noAuthen")
    public String noAuthen(){
        return "权限不足";
    }

    /**
     * 模拟跳转用户登录页面
     * @return
     */
    @GetMapping("/login2")
    public String login(){
        return "用户登录页面";
    }


    @RequiresPermissions("/exam/paperManager")
    @GetMapping("/paper")
    public String paper(){

        return "试卷管理";
    }
}

6、启动项目,就可以测试了。

猜你喜欢

转载自blog.csdn.net/hyly_zhard/article/details/100087013
今日推荐