spring boot集成security

spring boot集成security

  • 1. security介绍
  • 2 为什么选择 Spring Security
  • 3. Security如何使用
    • 3.1 创建
    • 3.2 配置
    • 3.3 security 配置
      • 3.3.1 configureGlobal方法
      • 3.3.2 启动登陆
      • 3.3.3 自定义配置 configure
      • 3.3.4 controller
      • 3.3.5 界面
    • 3.4 启动
  • 4. security 方法保护
    • 4.1 创建实体
    • 4.2 创建服务
    • 4.3 创建controller
    • 4.4 访问验证
  • 5. 从数据库中读取用户认证信息
    • 5.1 创建
    • 5.2 配置
    • 5.3 创建实体
    • 5.4 dao
    • 5.5 service
    • 5.6 config
    • 5.7 启动验证
  • 6. 总结

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的关系如下:
spring boot集成security_第1张图片

3. Security如何使用

3.1 创建

spring boot集成security_第2张图片

3.2 配置

spring boot集成security_第3张图片

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里面配置的用户名、密码与权限的信息。
这里最好不要硬编码。
spring boot集成security_第4张图片

3.3.1 configureGlobal方法

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

    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 启动登陆

spring boot集成security_第6张图片
其源码如下
spring boot集成security_第7张图片
写一个简单的html界面用来标识登陆成功。
使用admin登陆
spring boot集成security_第8张图片

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


<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login pagetitle>
    <base href="/">
    <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
head>
<body>
    <h1>Login pageh1>
    <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


<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 indextitle>
    <base href="/">
    <link rel="stylesheet" href="css/main.css" th:href="@{/css/main.css}"/>
head>
<body>
    <h1>Hello Spring Boot Security for indexh1>
    <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


<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 Pagetitle>
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


<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 Indextitle>
    <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目录下的indexa> p>
body>
html>

/admin/index.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 Indextitle>
    <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目录下的indexa> p>
body>
html>

3.4 启动

spring boot集成security_第9张图片
不登陆访问/user或者admin的界面
spring boot集成security_第10张图片
访问admin的界面登陆user用户(user用户只有user角色)
spring boot集成security_第11张图片
相反的,访问user界面,登陆admin用户(admin用户有user和admin的角色)
spring boot集成security_第12张图片
访问admin下的界面
spring boot集成security_第13张图片
然后登出
spring boot集成security_第14张图片
登陆user角色访问user界面
登陆失败
spring boot集成security_第15张图片
spring boot集成security_第16张图片
然后访问admin的界面
spring boot集成security_第17张图片

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 创建服务

spring boot集成security_第18张图片

4.3 创建controller

spring boot集成security_第19张图片

4.4 访问验证

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

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

5.1 创建

spring boot集成security_第25张图片
为了防止因为字符集的问题,需要手动增加依赖

5.2 配置

spring boot集成security_第26张图片

5.3 创建实体

spring boot集成security_第27张图片
spring boot集成security_第28张图片

@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

spring boot集成security_第29张图片

5.5 service

spring boot集成security_第30张图片

5.6 config

spring boot集成security_第31张图片

5.7 启动验证

这是启动项目前的数据库中所有的表
spring boot集成security_第32张图片
接着启动

spring boot集成security_第33张图片
中间关系表与我们猜想的一致
spring boot集成security_第34张图片
不过有一点没有想到,这个中间关系表竟然有外键。

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

我们插入上述数据:
spring boot集成security_第35张图片
在这里插入图片描述
spring boot集成security_第36张图片
spring boot集成security_第37张图片
接下来用这些用户尝试登陆,并且结合4中的逻辑,进行验证。
spring boot集成security_第38张图片

改动点如上图所示
spring boot集成security_第39张图片
登陆
userA
spring boot集成security_第40张图片
spring boot集成security_第41张图片
adminA
spring boot集成security_第42张图片
spring boot集成security_第43张图片
allA
spring boot集成security_第44张图片
spring boot集成security_第45张图片

注意点:
这里面有两个坑:
1.password需要返回加密方式:
spring boot集成security_第46张图片
原因:
spring boot集成security_第47张图片
可选
spring boot集成security_第48张图片
2.role返回的时候需要加前缀
spring boot集成security_第49张图片
原因:
spring boot集成security_第50张图片
使用配置的时候会自动加这个前缀,现在使用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这种方式。

你可能感兴趣的:(spring,boot,微服务,Security)