Spring Security 上

Security-dome

1.创建项目

创建一个Spring Boot项目,不用加入什么依赖

2.导入依赖

<dependencies>
<!--启动器变为 web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--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>

3.创建控制层

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

4.配置文件修改端口号

server.port=8081

5.运行测试

运行网址为:

http://localhost:8081/hello

这时候会发现,网址会自动变为:

http://localhost:8081/login

6.登录

能看到,在该页面中有账号密码

默认账号:user

默认密码:

登录之后:

Security 原理

Spring Security 本质是一个过滤器链

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


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


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


过滤器加载步骤

步骤流程

使用Spring Security配置过滤器 : DelegatingFilterProxy

源代码如下

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized(this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = this.findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
} delegateToUse = this.initDelegate(wac); }
this.delegate = delegateToUse;
}
}
this.invokeDelegate(delegateToUse, request, response, filterChain);
}

即为:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//进行判断 //初始化
delegateToUse = this.initDelegate(wac); //其余部分
}

然后我们查看 initDelegate

初始化为 FilterChainProxy 对象

进入 FilterChainProxy:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
//满足条件 运行该方法
this.doFilterInternal(request, response, chain); } else {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//不满足 最终还是需要运行该方法
this.doFilterInternal(request, response, chain); } catch (RequestRejectedException var9) {
this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, var9);
} finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
}

可以看出,无论满不满足条件,最终都需要运行 doFilterInternal()方法

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/*
* 部分代码。。。
*/
List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
/*
* 部分代码。。。
*/
    private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
Iterator var3 = this.filterChains.iterator();
SecurityFilterChain chain;
do {
if (!var3.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var3.next();
if (logger.isTraceEnabled()) {
++count;
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, count, this.filterChains.size()));
}
} while(!chain.matches(request));
//返回所有过滤器
return chain.getFilters();
}

所以 doFilterInternal() 方法 可以返回 所有要进行加载的过滤器


总结:

  1. 配置过滤器 DelegatingFilterProxy
  2. 在其中进行初始化 initDelegate
  3. 在初始化中得到 FilterChainProxy 对象
  4. 在其中运行的就是 doFilterInternal() 方法,该方法返回的就是 所有要进行加载的过滤器

UserDetailsService 接口

UserDetailsService接口 : 查询数据库用户名和密码过程

步骤:

  1. 创建类继承UsernamePasswordAuthenticationFilter,重写三个方法: attemptAuthentication() 、successfulAuthentication()、unsuccessfulAuthentication()
  2. 如果成功调用successfulAuthentication(),反之调用unsuccessfulAuthentication()
  3. 创建类实现UserDetailService,编写查询数据过程,返回User对象,这个User对象是安全框架提供对象

PasswordEncoder接口

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

加密方法:

BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时多使用这个解析器。

BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认10.

BCryptPasswordEncoder b = new BCryptPasswordEncoder();
String zc = b.encode("zc"); //加密成功

Web权限

Security-dome 中可以看到,如果想要进入页面,还需要输入账号密码

而对于登陆时候的账号密码可以进行自定义设置

  1. 通过配置文件
  2. 通过配置类
  3. 自定义编写实现类

1.通过配置文件

spring.security.user.name=root
spring.security.user.password=root

这个时候再运行,会发现控制台不会出现密码,可以直接通过设置的账号密码登录

2.通过配置类

  1. 创建一个 SecurityConfig 配置类
  2. 重写configure()方法,注意看清参数,不要选错方法
  3. 很重要的一点:需要注入PasswordEncoder接口

如果不注入该接口,可能报 Encoded password does not look like BCrypt

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String password = bCryptPasswordEncoder.encode("root");
auth.inMemoryAuthentication()
.withUser("root") //账号
.password(password) //加密的密码
.roles("admin"); //权限
} @Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}

这时候,也可以直接使用你设置的账号密码登录页面

3.自定义编写实现类

  1. 编写userDetailsService实现类,返回User对象
  2. 创建一个 SecurityConfig 配置类

编写一个UserDetailsService实现类

在其中需要重写 loadUserByUsername() 方法,该方法用于登录

@Service("userDetailsService")
public class MyuserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("role");
//返回的实际上是一个User对象,参数解析可以看下面
return new User("root",
new BCryptPasswordEncoder().encode("root"),auths);
}
}

UserDetailsService 解析

对于该实现类中重写的 loadUserByUsername() 方法,返回的是 UserDetails 接口

在源代码中可以看出,实际上 UserDetails 接口,返回的是一个 User 对象

而在User对象中,需要返回三个参数:

String、String、Collection;

账号 、 密码 、集合(权限等信息)


创建一个 SecurityConfig 配置类

@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter { @Autowired
UserDetailsService userDetailsService; // 这里应和 @Service("userDetailsService") 中内容相同 @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用该方法
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
} @Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}

这时候测试,也可以直接使用设置的账号密码登录

之后,如果连接数据库,一般都是用第三种方式

4.连接数据库完成用户认证

(该方法是在第三种方法代码基础上完成)

  1. 创建数据库
  2. 整合Mybatis-Plus完成数据库操作
  3. 配置JDBC信息
  4. 创建实体类、Mapper接口
  5. 创建UserDetailsService类

创建数据库

创建了一个 mybatis-plus 数据库 ,其中创建了一个users表,记得创建后,加入数据

引入依赖

<!-- Mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

配置JDBC信息

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis-plus?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

创建实体类、Mapper接口

@Data    // 引入了Lombok才可以使用
public class Users {
private Integer id;
private String username;
private String password;
}
@Repository
@Mapper
public interface UsersMapper extends BaseMapper<Users> {
}

创建UserDetailsService类

@Service("userDetailsService")
public class MyuserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper; @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用usersMapper方法
QueryWrapper<Users> wrapper = new QueryWrapper<>();
wrapper.eq("username",username);
Users users = usersMapper.selectOne(wrapper);
if (users == null){
//数据库没有用户名,认证失败
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(users.getUsername(),
new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}
}

这时候就可以正常运行了

5.自定义登录页面

在上面代码的基础上完成该部分代码

1.创建前端页面

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

2.书写Controller层代码

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

3.在创建的配置类中重写 configure(HttpSecurity http) 方法

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

这时候可以分别测试进入以下两个路径:

http://localhost:8081/hello

http://localhost:8081/index

会发现,第一个 hello 路径 ,不会拦截了,可以直接进入页面

第二个index,会进入自定义的登陆页面,登陆成功后,才可以进入

基于角色或权限的访问控制

1.hasAuthority方法

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

  1. 修改配置类
  2. 在 UserDetailsService 实现类中添加权限
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/index").permitAll()
.and().authorizeRequests()
.antMatchers("/","/hello","/user/login")
.permitAll() //当前登录用户,只有具有admins权限才可以访问这个路径
.antMatchers("/index").hasAuthority("admins") .anyRequest().authenticated()
.and().csrf().disable();
}
    @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<Users> wrapper = new QueryWrapper<>();
wrapper.eq("username",username);
Users users = usersMapper.selectOne(wrapper);
if (users == null){
throw new UsernameNotFoundException("用户名不存在");
} List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admins"); //这里添加权限
return new User(users.getUsername(),
new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}

进行测试,路径为:

http://localhost:8081/index

  • 如果权限不通过 , 403 无权限

  • 如果权限通过 ,正常运行

2.hasAnyAuthority方法

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

与 hasAuthority() 的区别是

  • hasAuthority() 参数唯一,只能满足这一个权限才可以

  • 而该方法,参数可以多个,满足其中一个权限 即为通过

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/index").permitAll()
.and().authorizeRequests()
.antMatchers("/","/hello","/user/login")
.permitAll() //当前登录用户,具有admins或者user权限才可以访问这个路径
.antMatchers("/index").hasAnyAuthority("admins","user") .anyRequest().authenticated()
.and().csrf().disable();
}

3.hasRole方法

如果用户具备给定角色就 允许访问,否则出现 403

如果当前主体具有指定的角色,则返回 true

该方法与 hasAuthority 方法,使用方法基本相同,区别就是 他需要在权限前加上 ROLE_

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/index").permitAll()
.and().authorizeRequests()
.antMatchers("/","/hello","/user/login")
.permitAll() //当前登录用户,只有具有 ROLE_user 权限才可以访问这个路径
.antMatchers("/index").hasRole("user") .anyRequest().authenticated()
.and().csrf().disable();
}
// UserDetailsService 实现类中添加权限
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_user");

这时候可以正常运行

4.hasAnyRole方法

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

该方法与 hasRole() 的区别 与1 2 两种方法相同,大家可以自行测试

5.自定义403页面

1.创建自定义403页面

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

2.修改配置类

@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedPage("/uuauth.html");
}

个人博客为:

MoYu's Github Blog

MoYu's Gitee Blog

Spring Security 上的更多相关文章

  1. 基于Spring Security 的JSaaS应用的权限管理

    1. 概述 权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源.资源包括访问的页面,访问的数据等,这在传统的应用系统中比较常见.本文介绍的则是基于Saas系统 ...

  2. Spring Security(三十五):Part III. Testing

    This section describes the testing support provided by Spring Security. 本节介绍Spring Security提供的测试支持. ...

  3. Spring Security 5.0.x 参考手册 【翻译自官方GIT-2018.06.12】

    源码请移步至:https://github.com/aquariuspj/spring-security/tree/translator/docs/manual/src/docs/asciidoc 版 ...

  4. 【JavaEE】SSH+Spring Security基础上配置AOP+log4j

    Spring Oauth2大多数情况下还是用不到的,主要使用的还是Spring+SpringMVC+Hibernate,有时候加上SpringSecurity,因此,本文及以后的文章的example中 ...

  5. Spring Security中异常上抛机制及对于转型处理的一些感悟

    在使用Spring Security的过程中,我们会发现框架内部按照错误及问题出现的场景,划分出了许许多多的异常,但是在业务调用时一般都会向外抛一个统一的异常出来,为什么要这样做呢,以及对于抛出来的异 ...

  6. Spring Security OAuth2 开发指南

    官方原文:http://projects.spring.io/spring-security-oauth/docs/oauth2.html 翻译及修改补充:Alex Liao. 转载请注明来源:htt ...

  7. SPRING SECURITY JAVA配置:Web Security

    在前一篇,我已经介绍了Spring Security Java配置,也概括的介绍了一下这个项目方方面面.在这篇文章中,我们来看一看一个简单的基于web security配置的例子.之后我们再来作更多的 ...

  8. 【OAuth2.0】Spring Security OAuth2.0篇之初识

    不吐不快 因为项目需求开始接触OAuth2.0授权协议.断断续续接触了有两周左右的时间.不得不吐槽的,依然是自己的学习习惯问题,总是着急想了解一切,习惯性地钻牛角尖去理解小的细节,而不是从宏观上去掌握 ...

  9. spring security oauth2.0 实现

    oauth应该属于security的一部分.关于oauth的的相关知识可以查看阮一峰的文章:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html ...

随机推荐

  1. 01.从0实现一个JVM语言之架构总览

    00.一个JVM语言的诞生过程 文章集合以及项目展望 源码github地址 这一篇将是架构总览, 将自顶向下地叙述自制编译器的要素; 文章目录 01.从0实现一个JVM语言之架构总览 架构总览目前完成 ...

  2. 使用 Java 开发 Gradle 插件

    Gradle 插件代码可以在 build.gradle 中,buildSrc 项目中,以及独立的插件项目中编写.本文将介绍如何在一个独立的项目中使用 Java 语言编写 Gradle 插件,并发布到仓 ...

  3. 在windows 下查看ip 地址和 在ubundu 下查看IP地址

    在windows 下查看ip 地址和 在ubundu 下查看IP地址 1.在windows 下查看 IP地址:ipconfig 2.在 ubundu 下查看IP地址:ifconfig

  4. docker配置私有镜像仓库-registry和hyper/docker-registry-web

    1.前言️​ Docker hub是远程仓库,是国外的,push pull速度特别慢,尤其是网速不好的时候,页面都点不进去,官网 但是可以配置阿里云镜像加速哦: 因此搭建一个私有的镜像仓库用于管理我们 ...

  5. 2017算法期末复习练习赛-G Beihang Couple Pairing Comunity 2017 题解(网络流)

    理解不够透彻.好题不可浪费,写题解以增进理解.会陆续补充题目.(咕咕咕) G Beihang Couple Pairing Comunity 2017 题目链接 Beihang Couple Pair ...

  6. springcloud知识点总结

    一.SpringCloud面试题口述1.SpringCloud和DubboSpringCloud和Dubbo都是现在主流的微服务架构SpringCloud是Apache旗下的Spring体系下的微服务 ...

  7. Python之内存泄漏和内存溢出

    预习知识:python之MRO和垃圾回收机制 一.内存泄漏 像Java程序一样,虽然Python本身也有垃圾回收的功能,但是同样也会产生内存泄漏的问题.对于一个用 python 实现的,长期运行的后台 ...

  8. canvas-修改图片亮度

    canvas操作-修改图片亮度 目录 canvas操作-修改图片亮度 图片亮度的概念 下面用ps截图举一个例子: 调整图片亮度的方案 实现方案一 从RGB到HSV的转换 转换的公式 javascrip ...

  9. 轻量易用的微信Sdk发布——Magicodes.Wx.Sdk

    概述 最简洁最易于使用的微信Sdk,包括公众号Sdk.小程序Sdk.企业微信Sdk等,以及Abp VNext集成. GitHub地址:https://github.com/xin-lai/Magico ...

  10. Amundsen在REA Group公司的应用实践

    REA Group是一家专门面向房地产与实业资产的跨国数字广告公司. 他们主要为消费者提供房地产购买.出售与租赁服务,同时发布各类房产新闻.装修技巧以及生活方式层面的内容.每一天,都有数百万消费者访问 ...