spring boot集成security


git地址
https://github.com/a18792721831/studySpringCloud.git

1. security介绍

Spring Security 是Spring Resource 社区的一个安全组件.Sping Secuity为JavaEE企业级开发提供了全面的安全防护,安全防护是一个不断变化的目标,Spring Security通过版本不断迭代来实现这一目标。Spine Sceunt采用"安全层”的概念,使每一层都尽可能安全,连续的安全层可以达到全面的防护。Spring Security可以在Contoller层、Service层,DAO层等以加注解的方式来保护应用程序的安全,Spring Security 提供了细粒度的权限控制,可以精细到每一个API接口、每一个业务的方法,或者每一个操作数据库的DAO层的方法.Spring Security提供的是应用程序层的安全解决方案,一个系统的安全还需要考患传输层和系统层的安全,例如采用Htpps协议、服务器部署防火墙等。

2 为什么选择 Spring Security

使用 Spring Securiy有很多原因,其中一个重要原因是它对环境的无依赖性、低代码耦合性。将工程重现部署到一个新的服务器上,不需要为 Spring Security做什么工作。Spring Security 提供了数十个安全模块,模块与模块间的耦合性低,模块之间可以自由组合来实现特定需求的安全功能,具有较高的可定制性。总而言之,Spring Security 具有很好的可复用性和可定制性。
在安全方面,有两个主要的领域,一是“认证”,即你是谁;二是“授权”,即你拥有什么权限,Spring Security 的主要目标就是在这两个领域。“认证”是认证主体的过程,通常是指可以在应用程序中执行操作的用户、设备或其他系统。“授权”是指决定是否允许已认证的主体执行某一项操作。
安全框架多种多样,那为什么选择 Spring Security 作为微服务开发的安全框架呢?JavaEE 有另一个优秀的安全框架 Apache Shiro,Apache Shiro 在企业级的项目开发中十分受欢迎,一般使用在单体服务中。但在微服务架构中,目前版本的 Apache Shiro是无能为力的Spring Security 来自 Spring Resource 社区,采用了注解的方式控制权限,熟悉Spring 的开发者很容易上手Spring Security。另外一个原因就是Spring Security易用与Spring boot工程,也容易集成到Spring Cloud构建的微服务系统中。

总结起来有以下几个特点:

    1. 代码耦合低
    1. 模块化
    1. 控制粒度细
    1. 熟悉spring容易上手
    1. spring boot 或者spring cloud的集成简单
    1. 庞大的社区与用户

Spring Security 和Spring Boot Security的关系如下:
在这里插入图片描述

3. Security如何使用

3.1 创建

在这里插入图片描述

3.2 配置

在这里插入图片描述

3.3 security 配置

security需要自己写一个配置类,配置类集成于WebSecurityConfigureAdapter

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private Environment environment;

    @Value("${web.security.user.name}")
    private String username;

    @Value("${web.security.user.pswd}")
    private String password;

    @Value("${web.security.user.role}")
    private String role;

    @Value("${web.security.admin.name}")
    private String adminName;

    @Value("${web.security.admin.pswd}")
    private String adminPswd;

    @Value("${web.security.admin.role}")
    private String adminRole;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser(username).password("{noop}" + password)
                .roles(role.split(","));
        auth.inMemoryAuthentication().withUser(adminName).password("{noop}" + adminPswd)
                .roles(adminRole.split(","));

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/css/**", "/index").permitAll()
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .and()
                .formLogin().loginPage("/login").failureUrl("/login-error")
                .and()
                .exceptionHandling().accessDeniedPage("/401")
                .and()
                .logout().logoutSuccessUrl("/");
    }

}

这里的私有属性是在config.properties里面配置的用户名、密码与权限的信息。
这里最好不要硬编码。
在这里插入图片描述

3.3.1 configureGlobal方法

这个方法中,在内存中创建2个用户的信息,用户的用户名、密码以及密码的加密方式,和其具有的角色。
密码的加密方式:
在这里插入图片描述
这个方法里只有短短的两行代码,但是其完成了非常多的操作:

    1. 应用的每一个请求都要认证
    1. 自动生成了一个登陆表单
    1. 用指定的用户名密码进行认证
    1. 用户可以注销
    1. 阻止了CSRF的攻击
    1. Session Fixation的保护
    1. 安全Header
    • HTTP Strict Transport Security for secure requests
    • X-Content-Type_Options integration
    • Cache Control
    • X-XSS-Protection integration
    • XFrake-Option integration to help prevent Clickjacking
    1. 集成了如下方法:
    • HttpServletRequest#getRemoteUser()
    • HttpServletRequest.html#getUserPrincipal()
    • HttpServletRequest.html#isUserInRole(String)
    • HttpServletRequest.html#login(String,String)
    • HttpServletRequest.html#logout()

3.3.2 启动登陆

在这里插入图片描述
其源码如下
在这里插入图片描述
写一个简单的html界面用来标识登陆成功。
使用admin登陆
在这里插入图片描述

3.3.3 自定义配置 configure

代码 配置内容
“/css/**”,"/index" 不需要认证即可访问
“/user/**” user目录下的界面需要验证user角色
“/admin” admin目录下的界面需要验证admin角色
formLogin 表单登陆界面是/login界面
failureUrl 登陆失败的地址是/login-error
exceptionHandling 异常会被重定向到401界面
logout 支持注销
logoutSuccessUrl 注销后重定向到/

3.3.4 controller

基于3.3.3的配置,实现controller

@Controller
public class MainController {

    @RequestMapping("/")
    public String root(){
        return "redirect:/index";
    }

    @RequestMapping("/index")
    public String index(){
        return "index";
    }

    @RequestMapping("/user/index")
    public String userIndex(){
        return "user/index";
    }

    @RequestMapping("/admin/index")
    public String adminIndex(){
        return "admin/index";
    }

    @RequestMapping("/login")
    public String login(){
        return "login";
    }

    @RequestMapping("/login-error")
    public String loginError(Model model){
        model.addAttribute("loginError",true);
        return "login";
    }

    @GetMapping("/401")
    public String accessDenied(){
        return "401";
    }
}

3.3.5 界面

login.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login page</title>
    <base href="/">
    <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
    <h1>Login page</h1>
    <p th:if="${loginError}" class="error">用户名或者密码错误!</p>
    <form th:action="@{/login}" method="post">
        <label for="username">用户名</label>:
        <input type="text" id="username" name="username" autofocus="autofocus"/><br/>
        <label for="password">密  码</label>:
        <input type="password" id="password" name="password" autofocus="autofocus"/><br/>
        <input type="submit" value="登录"/>
    </form>
    <p><a th:href="@{/index}">返回首页</a> </p>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>Hello Spring Boot Security for index</title>
    <base href="/">
    <link rel="stylesheet" href="css/main.css" th:href="@{/css/main.css}"/>
</head>
<body>
    <h1>Hello Spring Boot Security for index</h1>
    <p>这个界面没有受到保护.</p>
    <div th:fragment="logout" sec:authorize="isAuthenticated()">
        登录用户:<span sec:authentication="name"/>
        用户角色:<span sec:authentication="principal.authorities"/>
        <div>
            <form action="#" th:action="@{/logout}" method="post">
                <input type="submit" value="登出" />
            </form>
        </div>
    </div>
    <ul>
        <li>点击<a href="/user/index" th:href="@{/user/index}">去/user/index被保护的界面</a> </li>
        <li>点击<a href="/admin/index" th:href="@{/admin/index}">去/admin/index被保护的界面</a> </li>
    </ul>
</body>
</html>

401.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>401 Page</title>
</head>
<body>
    <div>
        <div>
            <h2>权限不够</h2>
        </div>
        <div sec:authorize="isAuthenticated()">
            <p>已有用户登录</p>
            <p>用户:<span sec:authentication="name" /></p>
            <p>角色:<span sec:authentication="principal.authorities"/></p>
        </div>
        <div sec:authorize="isAnonymous()">
            <p>未有用户登录</p>
        </div>
        <p>
            拒绝访问!
        </p>
    </div>
</body>
</html>

/user/index.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello Spring Security, User Index</title>
    <base href="/">
    <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}"/>
</head>
<body>
    <div th:substituteby="index::logout"/>
    <h1>这个界面是被保护界面,user角色可以访问</h1>
    <p><a href="/index" th:href="@{/index}">返回首页</a> </p>
    <p><a href="/admin" th:href="@{/admin/index}">去admin目录下的index</a> </p>
</body>
</html>

/admin/index.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello Spring Security, Admin Index</title>
    <base href="/">
    <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}"/>
</head>
<body>
<div th:substituteby="index::logout"/>
<h1>这个界面是被保护界面,admin角色可以访问</h1>
<p><a href="/index" th:href="@{/index}">返回首页</a> </p>
<p><a href="/admin" th:href="@{/user/index}">去user目录下的index</a> </p>
</body>
</html>

3.4 启动

在这里插入图片描述
不登陆访问/user或者admin的界面
在这里插入图片描述
访问admin的界面登陆user用户(user用户只有user角色)
在这里插入图片描述
相反的,访问user界面,登陆admin用户(admin用户有user和admin的角色)
在这里插入图片描述
访问admin下的界面
在这里插入图片描述
然后登出
在这里插入图片描述
登陆user角色访问user界面
登陆失败
在这里插入图片描述
在这里插入图片描述
然后访问admin的界面
在这里插入图片描述

4. security 方法保护

4.1 创建实体

public class Student {

    private String name;

    private int age;

    private String like;

    private Student(){

    }

    public static Student getBuild(){
        return new Student();
    }

    public Student name(String name){
        this.name = name;
        return Student.this;
    }

    public Student age(int age){
        this.age = age;
        return Student.this;
    }

    public Student like(String like){
        this.like = like;
        return Student.this;
    }

    public String getName(){
        return this.name;
    }

    public int getAge(){
        return this.age;
    }

    public String getLike(){
        return this.like;
    }

}

4.2 创建服务

在这里插入图片描述

4.3 创建controller

在这里插入图片描述

4.4 访问验证

可以看到,我们在service上有两个访问,一个是获取全部的学生的getStudentList的方法,这个方法只要有任意一个权限就能够访问。而另一个方法则必须拥有ADMIN的权限的用户登录才能进行访问。
首先以USER权限进行登录:
在这里插入图片描述
然后获取所有的用户
在这里插入图片描述
然后进行尝试删除学生–小美
在这里插入图片描述
发现其在controller接收到请求调用服务时,因权限不够而发生异常,但是我们之前在配置时配置,当有异常出现时,自动重定向到401的界面。
所以,其展示的urlk地址是删除的地址,但是界面的内容确是,401的内容。
接下来使用admin权限的用户进行登录,然后尝试删除学生。
在这里插入图片描述
在这里插入图片描述
这里没有任何返回值,表示已经删除成功了,接下来重新获取所有的学生:
在这里插入图片描述

5. 从数据库中读取用户认证信息

5.1 创建

在这里插入图片描述
为了防止因为字符集的问题,需要手动增加依赖
在这里插入图片描述

5.2 配置

在这里插入图片描述

5.3 创建实体

在这里插入图片描述
在这里插入图片描述

@Entity
public class Subscriber implements UserDetails, Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false, unique = true)
    private String username;

    /*
     * OneToMany 是一对多的关系,关系由多的记录的属性维护(一般情况)
     * ManyToMany 是多对多的关系,关系由中间关系表维护(一般情况)
     */

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "subscriber_role", joinColumns = @JoinColumn(name = "subscriber_id", referencedColumnName = "id"),
    inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    /*
     * 这个JoinTable的大概含义是:
     * 这个中间关系由关系表维护,表名是 user_role
     * 关系表有两个字段,一个是 user_id,其映射的值是user表的id
     * 另一个是role_id,其映射的值是role表的id
     */
    private List<Role> authorities;

    public Subscriber(){

    }

    public Long getId(){
        return id;
    }

    public void setId(Long id){
        this.id = id;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(List<Role> authorities){
        this.authorities = authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password){
        this.password = password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username){
        this.username = username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

5.4 dao

在这里插入图片描述

5.5 service

在这里插入图片描述

5.6 config

在这里插入图片描述

5.7 启动验证

这是启动项目前的数据库中所有的表
在这里插入图片描述
接着启动
在这里插入图片描述
在这里插入图片描述
中间关系表与我们猜想的一致
在这里插入图片描述
不过有一点没有想到,这个中间关系表竟然有外键。

用户 权限
userA USER
userB USER
userC USER
adminA ADMIN
adminB ADMIN
adminC ADMIN
allA USER
allA ADMIN
allB USER
allB ADMIN
allC USER
allC ADMIN

我们插入上述数据:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接下来用这些用户尝试登陆,并且结合4中的逻辑,进行验证。
在这里插入图片描述

改动点如上图所示
在这里插入图片描述
登陆
userA
在这里插入图片描述
在这里插入图片描述
adminA
在这里插入图片描述
在这里插入图片描述
allA
在这里插入图片描述
在这里插入图片描述

注意点:
这里面有两个坑:
1.password需要返回加密方式:
在这里插入图片描述
原因:
在这里插入图片描述
可选
在这里插入图片描述
2.role返回的时候需要加前缀
在这里插入图片描述
原因:
在这里插入图片描述
使用配置的时候会自动加这个前缀,现在使用jpa则不会自动加前缀

6. 总结

使用Spring Security 还是比较简单的,没有想象中那么复杂。首先引入 Spring Security相关的依赖,然后写一个配置类,该配置类继承了 WebSecurityConfigurerAdapter,并在该配置类上加@EnableWebSecurity 注解开启 Web Security。再需要配置 AuthenticationManagerBuilder,AuthenticationManagerBuilder 配置了读取用户的认证信息的方式,可以从内存中读取,也可以
从数据库中读取,或者用其他的方式。其次,需要配置 HttpSecurity,HttpSecurity 配置了请求的认证规则,即哪些 URI 请求需要认证、哪些不需要,以及需要拥有什么权限才能访问。最后,如果需要开启方法级别的安全配置,需要通过在配置类上加@EnableGlobalMethodSccuriy注解开启,方法级别上的安全控制支持secureEnabled、jsr250Enabled和 prePostEnabled这3种
方式,用的最多的是prePostEnabled。其中,prePostEnabled 包括PreAuthorize和 PostAuthorize两种形式,一般只用到PreAuthorize这种方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章