SpringSecurity-Shiro对比学习

一、安全简介

在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。

市面上存在比较有名的:Shiro,Spring Security

这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。

Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求

从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。

怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。

Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

扫描二维码关注公众号,回复: 13551888 查看本文章

二、SpringSecurity

2.1 项目搭建

2.1.1 引入maven依赖

<!--导入thymeleaf依赖-->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.11.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

<!--spring security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

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

2.1.2 静态资源准备

index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>首页</title>
    <!--semantic-ui-->
    <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
    <link th:href="@{/style/css/qinstyle.css}" rel="stylesheet">
</head>
<body>

<!--主容器-->
<div class="ui container">

    <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
        <div class="ui secondary menu">
            <a class="item"  th:href="@{/index}">首页</a>

            <!--登录注销-->
            <div class="right menu">
                <!--未登录-->
                <a class="item" th:href="@{/toLogin}">
                    <i class="address card icon"></i> 登录
                </a>
                <!--注销-->
                <a class="item" th:href="@{/logout}">
                    <i class="sign-out icon"></i> 注销
                </a>
                <!--已登录
                <a th:href="@{/usr/toUserCenter}">
                    <i class="address card icon"></i> admin
                </a>
                -->
            </div>
        </div>
    </div>

    <div class="ui segment" style="text-align: center">
        <h3>Spring Security Study by yeyoo</h3>
    </div>

    <div>
        <br>
        <div class="ui three column stackable grid">
            <div class="column">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 1</h5>
                            <hr>
                            <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                            <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                            <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>
    
</div>


<script th:src="@{/style/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/style/js/semantic.min.js}"></script>

</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>登录</title>
    <!--semantic-ui-->
    <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
</head>
<body>

<!--主容器-->
<div class="ui container">

    <div class="ui segment">

        <div style="text-align: center">
            <h1 class="header">登录</h1>
        </div>

        <div class="ui placeholder segment">
            <div class="ui column very relaxed stackable grid">
                <div class="column">
                    <div class="ui form">
                        <form th:action="@{/usr/login}" method="post">
                            <div class="field">
                                <label>Username</label>
                                <div class="ui left icon input">
                                    <input type="text" placeholder="Username" name="username">
                                    <i class="user icon"></i>
                                </div>
                            </div>
                            <div class="field">
                                <label>Password</label>
                                <div class="ui left icon input">
                                    <input type="password" name="password">
                                    <i class="lock icon"></i>
                                </div>
                            </div>
                            <input type="submit" class="ui blue submit button"/>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <div style="text-align: center">
            <div class="ui label">
                </i>注册
            </div>
            <br><br>
            <small>blog.yeyoo.com</small>
        </div>
        <div class="ui segment" style="text-align: center">
            <h3>Spring Security Study by yeyoo</h3>
        </div>
    </div>


</div>

<script th:src="@{/style/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/style/js/semantic.min.js}"></script>

</body>
</html>
level.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>首页</title>
    <!--semantic-ui-->
    <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
    <link th:href="@{/style/css/qinstyle.css}" rel="stylesheet">
</head>
<body>

<!--主容器-->
<div class="ui container">

    <div th:replace="~{index::nav-menu}"></div>

    <div class="ui segment" style="text-align: center">
        <h3>Level-1-1</h3>
    </div>

</div>


<script th:src="@{/style/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/style/js/semantic.min.js}"></script>

</body>
</html>

复制八份用来测试权限
在这里插入图片描述

2.1.3 权限配置

我们需要 首页所有人可以访问,功能页只有对应权限的人才能访问
测试用例
拥有v1的权限可以访问level1下的文件
拥有v2的权限可以访问level2下的文件
拥有v3的权限可以访问level3下的文件

SecurityConfig
package com.SpringSecurity.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@EnableWebSecurity
//授权配置
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
//    @Autowired
//    DataSource dataSource;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        //首页所有人可以访问,功能页只有对应权限的人才能访问
        //请求授权的规则
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("v1")
                .antMatchers("/level2/**").hasRole("v2")
                .antMatchers("/level3/**").hasRole("v3");

        //没有权限默认会跳到登录页面,需要开启登录的页面
        http.formLogin();
        //防止网站攻击:get;post
        http.csrf().disable();//关闭csrf(跨站请求伪造)功能,登出失败可能产生的原因
        //开启注销功能
        http.logout().logoutSuccessUrl("/");
    }

    //认证配置
    //认证,springboot 2.1.x可以直接使用,其他版本会报错(或者采用下面的密码编码解决)
    //密码编码:PasswordEncoder
    //在spring security 5.0+新增了很多的加密方法
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        //数据库认证
//        auth.jdbcAuthentication().dataSource(dataSource).withDefaultSchema()
//                .passwordEncoder(new BCryptPasswordEncoder())
//                .withUser(User.withUsername("user").password("password").roles("roles"));

        //内存认证
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("yeyoo").password(new BCryptPasswordEncoder().encode("123456")).roles("v1", "v2")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("v1", "v2","v3");
    }
}

2.1.4 业务编写

RouterController 
@Controller
public class RouterController {
    
    
    //使得访问/,/index,/index.html都能跳到主页
    @RequestMapping({
    
    "/","/index","/index.html"})
    public String index(){
    
    
        return "index";
    }
    @RequestMapping("/toLogin")
    public String toLogin(){
    
    
        return "views/login";
    }
    //实现对level的三个页面的跳转,下面也是如此
    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id){
    
    
        return "views/level1/"+id;
    }
    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id){
    
    
        return "views/level2/"+id;
    }
    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id){
    
    
        return "views/level3/"+id;
    }
}

2.2 知识点整理

2.2.1 SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略

  • AuthenticationManagerBuilder:自定义认证策略

  • @EnableWebSecurity:开启WebSecurity模式

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

2.2.2 流程总结

  1. 引入 Spring Security 模块
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 编写 Spring Security 配置类
    参考官网:https://spring.io/projects/spring-security

查看我们自己项目中的版本,找到对应的帮助文档:

https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4

  1. 编写基础配置类 SecurityConfig

@EnableWebSecurity // 开启WebSecurity模式

  1. 定制请求的授权规则

三、Shiro

3.1 Shiro架构设计

在这里插入图片描述
为了简化配置并启用灵活配置/可插性,Shiro的实现都是高度模块化设计——由于如此的模块化,SecurityManager实现(以及它的类层次结构)并没有做很多事情。相反,SecurityManager 实现主要是作为一个轻量级的“容器”组件,委托计划所有的行为到嵌套/包裹的组件。
在这里插入图片描述
Shiro 核心三大组件

  • Subject
  • SecurityManager
  • Realm

Subject

Subject实质上是一个当前执行用户的特定的安全“视图”。鉴于"User"一词通常意味着一个人,而一个Subject可以是一个人,但它还可以代表第三方服务,daemon account,cron job,或其他类似的任何东西——基本上是当前正与软件进行交互的任何东西。

所有Subject实例都被绑定到(且这是必须的)一个SecurityManager上。当你与一个Subject交互时,那些交互作用转化为与SecurityManager交互的特定subject的交互作用。

SecurityManager

SecurityManager是Shiro架构的心脏,并作为一种“保护伞”对象来协调内部的安全组件共同构成一个对象图。然而,一旦SecurityManager和它的内置对象图已经配置给一个应用程序,那么它单独留下来,且应用程序开发人员几乎使用他们所有的时间来处理Subject API。

重要的是要认识到,当你正与一个Subject进行交互时,实质上是幕后的 SecurityManager处理所有繁重的Subject安全操作。这反映在上面的基本流程图。

简单的来说SecurityManager就是控制 Subject可以访问哪些资源。

Realms

Realms担当Shiro和你的应用程序的安全数据之间的“桥梁”或“连接器”。当它实际上与安全相关的数据如用来执行身份验证(登录)及授权(访问控制)的用户帐户交互时,Shiro 从一个或多个为应用程序配置的Realm中寻找许多这样的东西。

在这个意义上说,Realm本质上是一个特定安全的DAO:它封装了数据源的连接详细信息,使Shiro所需的相关的数据可用。当配置Shiro时,你必须指定至少一个Realm用来进行身份验证和/或授权。SecurityManager可能配置多个Realms,但至少有一个是必须的。

Shiro提供了立即可用的Realms来连接一些安全数据源(即目录),如LDAP,关系数据库(JDBC),文本配置源,像 INI 及属性文件,以及更多。你可以插入你自己的Realm 实现来代表自定义的数据源,如果默认地Realm不符合你的需求。

像其他内置组件一样,Shiro SecurityManager控制 Realms是如何被用来获取安全和身份数据来代表 Subject 实例的。

3.2 整合SpringBoot设计思路

在这里插入图片描述

3.3 项目搭建

3.3.1 核心pom依赖

<!--shiro-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.2</version>
</dependency>
<!--shiro-thymeleaf整合-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

3.3.2 Shiro 配置

UserRealm

public class UserRealm extends AuthorizingRealm {
    
    
    @Autowired
    private IUserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        System.out.println("执行了 => 授权");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermission("user:add");
        Subject subject = SecurityUtils.getSubject();
        //拿到user
        User currentUser =(User)subject.getPrincipal();
        //设置权限 数据库拿
        info.addStringPermissions(Arrays.asList(currentUser.getPerms().split(",")));
        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
        System.out.println("执行了 => 认证");
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        //用户名,密码去数据库取
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper = wrapper.eq(User::getName, userToken.getUsername());
        User user = userService.getOne(wrapper);
        if (user == null) {
    
      //没有这个人
            return null;  //其实就是抛出UnknownAccountException异常
        }
        ByteSource passwordSalt = ByteSource.Util.bytes(userToken.getUsername());//这里的参数要给个唯一的;
        //之后密码认证,shiro   它自己会做
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPwd(), passwordSalt,"");
        Subject currentSubject = SecurityUtils.getSubject();
        Session session = currentSubject.getSession();
        session.setAttribute("loginUser", user);
        return info;
    }
}

ShiroConfig

  • 创建reaml 对象 需要自定义 第一步
@Bean(name = "userRealm")
public UserRealm userRealm() {
    
    
    return new UserRealm();
}
  • DefaultWebSecurityManner 第二步
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
    
    
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    //关联UserRealm
    manager.setRealm(userRealm);
    return manager;
}
  • ShiroFilterFactoryBean 第三步
    Shiro内置过滤器

       anon:无需认证就可访问
       authc: 必须认证才可访问
       user: 必须拥有记住我才可访问
       perms: 拥有对某个资源权限才能访问
       role:拥有某个角色权限才能访问
    
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
    
    
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro内置过滤器
        //拦截
        HashMap<String, String> map = new LinkedHashMap<>();

        //授权
        map.put("/user/add","perms[user:add]");
        map.put("/user/update","perms[user:update]");
//        map.put("/user/logout","logout");
        map.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(map);
        //设置登录请求
        bean.setLoginUrl("/toLoginShiro");
        //设置未授权页面
        bean.setUnauthorizedUrl("/unauthorized");
        return bean;
    }

3.3.3 业务编写

用户登录页面-不同的用户对应不同的权限-不同的权限展示不同的按钮
未登录之前点击任何按钮调转登录
显示的按钮 若当前用户无权访问 跳转无权访问页面
在这里插入图片描述

如上图 路径跳转以及权限配置在 config中标明

@Controller
public class UserController {
    
    

    @RequestMapping("/shiroIndex")
    public String shiroIndex(Model model) {
    
    
        model.addAttribute("msg", "shiro test");
        return "shiro/shiroIndex";
    }

    @RequestMapping("/user/add")
    public String shiroAdd() {
    
    
        return "shiro/shiroAdd";
    }

    @RequestMapping("/user/update")
    public String shiroUpdate() {
    
    
        return "shiro/shiroUpdate";
    }

    @RequestMapping("/toLoginShiro")
    public String toLogin() {
    
    
        return "shiro/shiroLogin";
    }

    @RequestMapping("/login")
    public String login(String username, String password, Model model) {
    
    
        //获取当前用户
        Subject currentSubject = SecurityUtils.getSubject();
        //封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //登录
        try {
    
    
            currentSubject.login(token);
            return "shiro/shiroIndex";
        } catch (UnknownAccountException e) {
    
    
            model.addAttribute("msg", "用户名不存在");
            return "shiro/shiroLogin";
        } catch (IncorrectCredentialsException e) {
    
    
            model.addAttribute("msg", "密码错误");
            return "shiro/shiroLogin";
        }
    }

    @RequestMapping("/unauthorized")
    @ResponseBody
    public String unauthorized(){
    
    
        return "无权访问";
    }

    @RequestMapping("/user/logout")
    public String logout(Model model){
    
    

        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        model.addAttribute("msg","安全退出!");
        return "shiro/shiroIndex";
    }
}

猜你喜欢

转载自blog.csdn.net/L_994572281_LYA/article/details/120879376