Spring Security框架(一)

Spring Security 中文文档 参考手册 中文版

1、概要 

Spring是非常流行和成功的Java应用开发框架,Spring Security正是Spring家族中的成员,Spring Security 基于Spring框架,提供了一套Web安全性的完整的解决方案。

正如你可能知道的关于安全方面的两个区域主要有“认证”和“授权”(或者访问控制),一般来说,Web应用的安全性包括用户认证(Authentication)和用户授权(Autheonization)两个部分,这两点也是Spring Security的重要核心功能。

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统,用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证的过程。通俗一点来讲就是系统认为用户是否能登录。

(2)用户授权指的是:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的,比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说系统为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

1.2.Spring Security对比 Shiro

Spring Securtiy 的特点:

             和Spring无缝整合

             全面的权限控制

             专门为Web开发而设计

             旧版本不能脱离Web环境使用,新版本对整个框架进行分层抽取,分为了核心模块和Web模块。单独引入核心模块就可以脱离Web环境。

             重量级

Shiro 的特点:(Apache旗下的轻量级权限控制框架)

              轻量级:Shiro主张的理念是把复杂的事情变简单。针对性能有更高要求的互联网应用有                  更好的表达性。

             通用性

             好处:不局限于Web环境,可以脱离Web环境使用。

             缺陷:在Web环境下一些特定的需求需要手动编写代码定制。

Spring Security是Spring家族中的一个安全管理框架,实际上,在Spring Boot出现之前,Spring Security就已经发展很多年了,但使用的并不多,安全管理这个领域,一直是Shiro的天下。

相对于Shiro,在SSM中整合Spring Security都是比较麻烦的操作。所以,SpringSecurity虽然功能比Shiro强大,但是没有Shiro多(Shiro虽然功能没有Spring Security多,但是大部分项目而言,Shiro就够了)

自从有了Spring boot之后,Spring boot对于Spring Security提供了自动化配置方案,可以使用更少的配置来使用Spring Security。

因此一般来说,常见的安全管理技术栈的组合是这样的:

SSM+Shiro

Spring Boot/Spring Cloud + Spring Security

以上只是一个推荐组合,如果单纯从技术上来说,无论这么组合都是可以运行的。

2.Spring Security 的 hello World

2.1、创建一个项目

2.2.导入pom.xml依赖

<?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.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zsq</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springSecurity-demo01</name>
    <description>springSecurity-demo01</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--Spring Security依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

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

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

</project>

2.3、编写一个Controller的方法,用来测试

package com.zsq.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {
    @GetMapping("/hello")
    public String hello(){
        return "hello security";
    }
}

2.4、配置一个springboot启动的端口

 注:开启启动类:访问刚刚Controller定义的方法,跳转到下面这个页面:

  http://localhost:8111/login

2.5、Spring Sercurity的基本原理:

Spring Sercurity本质是一个过滤器链:

FilterSecurityInterceptor:是一个方法级的权限过滤器,基本位于过滤链的最底部。

ExceptionTranslationFilter:是一个异常过滤器,用来处理在认证授权的过程中抛出的异常

UsernamePasswordAuthenticationFilter:对/login的post请求做拦截,校验表单中用户名,密码。

 2.6、UserDetailsService接口讲解

当什么也不用配置的时候,账号和密码由Spring Securtiry定义生成的,而在实际项目中账号和密码都是从数据库中查询出来的,所以我们要通过自定义逻辑控制认证逻辑。

如果需要自定义逻辑时,只需要实现UserDetailsService接口即可,接口定义如下:

创建类继承UsernamePasswordAuthenticationFilter,重写三个方法,创建类实现UserDetailsService,编写查询数据过程,返回User对象,这个User对象是安全框架提供对象

PasswordEncoder数据加密接口,用于返回User对象里面密码加密

方式二:编写类实现方法

package com.zsq.service;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new User("zsq",new BCryptPasswordEncoder().encode("123"),auths);
    }
}

3.实现数据库认证来完成用户登录

3.1、在pom.xml中导入相关依赖

  <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--lombok用来简化实体类-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

3.2、创建一个数据库,和一张用户表

 3.3、创建相对表的实体类

package com.zsq.entity;

import lombok.Data;

@Data
public class Users {
    private int id;
    private String username;
    private String password;
}

3.4、整合Mapper,创建接口,继承mapper的接口

package com.zsq.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zsq.entity.Users;

public interface UsersMapper extends BaseMapper<Users> {
}

3.5、在MyUserDetailsService 调用Mapper里面的方法查询数据库进行认证

package com.zsq.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zsq.entity.Users;
import com.zsq.mapper.UsersMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //调用userMapper里面的方法查询数据库
        QueryWrapper<Users> queryWrapper = new QueryWrapper();
        queryWrapper.eq("username",username);
        Users users = usersMapper.selectOne(queryWrapper);
        //判断
        if(users == null){//数据库没有用户名,认证失败
            throw new UsernameNotFoundException("用户不存在!");
        }
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        //从查询数据库返回user的对象,得到用户名和密码
        return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
    }
}

3.6、在启动类上加上MapperScaner注解,添加mapper类的位置

package com.zsq;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.zsq.mapper")
public class SpringSecurityDemo01Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityDemo01Application.class, args);
    }

}

3.7、在配置类配置mysql的连接

server.port=8111

#mysql数据连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=ht@JH731

4.自定义设置登录页面不需要认证可以访问

4.1、在配置类实现相关的配置

package com.zsq.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import sun.security.util.Password;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
    @Bean
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() //自定义自己编写的登录页面
                .loginPage("/login.html")   //登录页面设置
                .loginProcessingUrl("/user/login")   //登录访问路径
                .defaultSuccessUrl("/test/index").permitAll()  //登录成功之后,跳转路径
                .and().authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
    }
}

4.2、创建相关的页面,controller

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
    用户名:<input type="text" name="username"/>
    <br/>
    密码:<input type="text" name="password"/>
    <br/>
    <input type="submit" value="login"/>

</form>

</body>
</html>

需要验证的结果:

不需要验证的结果:

5.基于权限和角色进行访问控制

基于角色或权限进行访问控制

5.1、hasAuthority方法 (针对某一个权限)

如果当前的主体具有指定的权限,则返回true,否则返回false

1.在配置类设置当前访问地址有哪些权限

 //当前登录用户,只有admins权限才可以访问这个路径
                .antMatchers("/test/index").hasAuthority("admins")

package com.zsq.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import sun.security.util.Password;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
    @Bean
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() //自定义自己编写的登录页面
                .loginPage("/login.html")   //登录页面设置
                .loginProcessingUrl("/user/login")   //登录访问路径
                .defaultSuccessUrl("/test/index").permitAll()  //登录成功之后,跳转路径
                .and().authorizeRequests()
                //当前登录用户,只有admins权限才可以访问这个路径
                .antMatchers("/test/index").hasAuthority("admins")
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
    }
}

2.在UserDetailsService,把返回User对象设置权限

List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");

package com.zsq.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zsq.entity.Users;
import com.zsq.mapper.UsersMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //调用userMapper里面的方法查询数据库
        QueryWrapper<Users> queryWrapper = new QueryWrapper();
        queryWrapper.eq("username",username);
        Users users = usersMapper.selectOne(queryWrapper);
        //判断
        if(users == null){//数据库没有用户名,认证失败
            throw new UsernameNotFoundException("用户不存在!");
        }
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
        //从查询数据库返回user的对象,得到用户名和密码
        return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
    }
}

5.2、hasAnyAuthority方法     (针对多个权限)  

如果当前主体有任何提供的角色,(给定的作为一个逗号分隔的字符串列表)的话,返回true

可以设置多个权限人

 .antMatchers("/test/index").hasAnyAuthority("admin","oms")

5.3、hasRole方法

如果用户具备给定角色就允许访问,否则出现403,如果当前主体具有指定的角色,则返回true.

.antMatchers("/test/index").hasRole("sale")

 注:必须加上ROLE_sale,不然会报错

5.4、hasAnyRole方法

表示用户具备任何一个条件都可以访问

给用户添加角色:

.antMatchers("/test/index").hasAnyRole("sale")
 List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,role,ROLE_sale");

5.5、没有权限,跳转到自定义的页面

自定义一个没有权限就跳转的页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>没有访问权限!</h1>
</body>
</html>

6、注解的使用

使用注解需要开启注解功能(放在启动类或者配置类都可)

@EnableGlobalMethodSecurity(securedEnabled = true)
1.@Secured:表示用户具有某一个角色可以访问的方法
 @GetMapping("update")
    @Secured({"ROLE_sale","ROLE_oms"})
    public String update(){
        return "hello zs1";
    }

在UserDetailsService中设置这个用户

2.@PreAuthorize:注解适合进入方法前的权限验证,@PreAuthorize可以将登录用户roles/permissions参数传到方法中

使用前要先开启@EnableGlobalMethodSecurity(prePostEnabled = true)注解

在controller中添加该注解


    @GetMapping("update")
    //@Secured({"ROLE_sale","ROLE_oms"})
    @PreAuthorize("hasAnyAuthority('admin')")
    public String update(){
        return "hello zs1";
    }

3.@PostAuthorize

使用前要先开启@EnableGlobalMethodSecurity(prePostEnabled = true)注解

@PostAuthorize注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限

 @GetMapping("update")
    //@Secured({"ROLE_sale","ROLE_oms"})
    @PreAuthorize("hasAnyAuthority('admin')")
    public String update(){
        System.out.println("update..");
        return "hello zs1";
    }

4.@PostFilter(方法返回数据进行过滤)

@PostFilter:权限验证之后对数据进行过滤留下用户名admin1的数据表达式中的filterObject引用的是方法返回值List中的某一个元素。

 @GetMapping("getAll")
    @PostAuthorize("hasAnyAuthority('admins')")
    @PostFilter("filterObject.username =='admin1'")
    public List<Users> getAllUsers(){
        ArrayList<Users> list = new ArrayList<>();
        list.add(new Users(11,"admin1","111111"));
        list.add(new Users(12,"admin2","9099999"));
        System.out.println(list);
        return list;
    }

5.@PreFilter(传入方法的数据进行过滤)

@PreFilter:进入控制器之前对数据进行过滤

@RequestMapping("getApp")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<Users> getApp(@RequestBody List<Users> list){
        list.forEach(t->{
             System.out.println(t.getId()+"\t"+t.getUsername());
})
           return list;
}

7、用户注销

1.在配置类添加退出配置

   //退出
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();

2.在页面上编写一个退出页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录成功
<a href="/logout">退出</a>

</body>
</html>

3.在配置类中添加退出映射地址

http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();

8.自动登录

1.创建一个数据表

create table persistent_logins (
     username varchar(64) not null,
     series varchar(64) primary key, 
     token varchar(64) not null,
     last_used timestamp not null)

 2.配置类,注入数据源,配置操作数据库对象


    //注入数据源
    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
       // jdbcTokenRepository.setCreateTableOnStartup(true);  在写的时候,创建表
        return jdbcTokenRepository;
    }

3.配置类,配置自动登录

.and().rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(30)//设置有效时长,以秒为单位
                .userDetailsService(userDetailsService)

4.在登录页面中添加一个复选框

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
    用户名:<input type="text" name="username"/>
    <br/>
    密码:<input type="text" name="password"/>
    <br/>
    <input type="submit" value="login"/>
    <br/>
    <input type="checkbox" name="remember-me"/>自动登录
    <br/>
    <input type="submit" value="login"/>
</form>
</body>
</html>

9.CSRF理解

      跨站请求伪造(英语:Cross-site request forgery),也被称为one-clickattack 或者session riding,通常缩写为CSRF或者XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方式,跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站上的信任,CSRF利用的是网站对用户网页浏览器的信任。

       跨站请求攻击,简单的说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发信息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。

       从Spring Security 4.0开始,默认情况下会启用CSRF保护,为防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT,和DELETE方法进行防护。

案例

 <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>

关闭安全配置中的csrf

// .and().csrf().disable(); //关闭csrf防护
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值