为了账号安全,请及时绑定邮箱和手机立即绑定

【备战春招】第二天 初识SpringSecurity

标签:
Java SpringBoot

目录

课程名称: Spring Security+OAuth2 精讲,打造企业级认证与授权

课程章节:第二章 初识SpringSecurity

课程讲师:接灰的电子产品

课程内容:

学习目标

  • 能够说出认证和授权的概念

  • 能够简单表述或用代码的演示资源的认证和授权流程

  • 能够简单表述过滤器链的作用

  • 了解SpringSecurity过滤器链的大致流程和常见的过滤器链

  • 熟悉HTTP请求与响应的结构

  • 熟悉HTTP Basic Auth认证流程

  • 可以解释什么是CSRF攻击

  • 可以处理登入登出的返回结果

  • 可以自定义过滤器链,实现某些功能

  • 可以看懂SecurityConfig中关于configure(HttpSecurity http) 的配置

  • 可以使用和配置Spring国际化

认证授权的概念

1.1 本章概述

| 认证和授权的概念 | Filter和FilterChain | Http | 实战 |

| :------------------------: | :--------------------------------------: | :-----------------------------------------------------------------------: | :--------------------------------------------------------: |

| ·认证解决“我是谁”的问题 | -Spring Security实现认证和授权的底层机制 | ·熟悉Http 的请求/响应的结构 | .新建工程·依赖类库 |

| 授权解决“我能做什么"的问题 | | ·Filter和窑户端交互(获取数据看,返回数据)是通过请求/响应中的字段完成的。 | 安全配置定制化登录页·CSRF攻击和保护·登录成功和失败后的处理 |

1.2 什么是认证

认证(Authentication)

认证

1.3 什么是授权

授权(Authorization)

授权

1.4 认证和授权过程的简单代码实现

1.定义资源


@RestController

@RequestMapping("/authorize")

public  class  AuthorizeResource  {

@GetMapping(value="greeting")

public  String  sayHello()  {

return  "hello world";

}

}

2.导入依赖


<!-- 用于开发 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-security</artifactId>

</dependency>

3.访问该资源

认证过程

4.授权的过程


  

@Slf4j

@RequiredArgsConstructor

@EnableWebSecurity(debug  =  true)

@Configuration

public  class  SecurityConfig  extends  WebSecurityConfigurerAdapter  {

private  final  ObjectMapper  objectMapper;

@Override

protected  void  configure(HttpSecurity  http)  throws  Exception  {

http

.authorizeRequests(authorizeRequests -> authorizeRequests

.antMatchers("/authorize/**").permitAll()

.antMatchers("/admin/**").hasRole("ADMIN")

.antMatchers("/api/**").hasRole("USER")

.anyRequest().authenticated())

.addFilterAt(restAuthenticationFilter(),  UsernamePasswordAuthenticationFilter.class);

}

}

2.2 过滤器和过滤器链

2.2.1 Spring Filters

任何Spring Web应用本质上只是一个 servlet

Security Filter在 HTTP请求到达你的Controller 之前过滤每一个传入的HTTP请求

Spring Filters

2.2.2 过滤器示例

过滤器示例

  1. 首先,过滤器需要从请求中提取一个用户名/密码。它可以通过一个基本的HTTP头,或者表单字段,或者cookie等等。

  2. 然后,过滤器需要对用户名/密码组合进行验证比如数据库。

  3. 在验证成功后,过滤器需要检查用户是否被授权访问请求的URI。

  4. 如果请求通过了所有这些检查,那么过滤器就可以让请求通过你的DispatcherServlet后重定向到@Controllers或者@RestController

2.2.3 Filter Chain

登录访问过滤器,认证过滤器,授权过滤器

过滤器链流程

2.2.4 常见的内建过滤器

BasicAuthenticationFilter:

·如果在请求中找到一个Basic Auth HTTP头,如果找到,则尝试用该头中的用户名和密码验证用户。

UsernamePasswordAuthenticationFilter

·如果在请求参数或者POST的Request Body中找到用户名/密码,则尝试用这些值对用户进行身份验证。

DefaultLoginPageGeneratingPtlter

·默认登录页面生成过滤器。用于生成一个登录页面,如果你没有明确地禁用这个功能,那么就会生成一个登录页面。这就是为什么在启用Spring Security时,会得到一个默认登录页面的原因。

DefaultLogoutPageGeneratingFilter

·如果没有禁用该功能,则会生成一个注销页面。

FilterSecurityInterceptor

·过滤安全拦截器。用于授权逻辑。

2.2.5 其他过滤器

JWTFilter(第五章会讲到的自建FIlter过滤器)

用于JWTToken的验证处理

3 http 请求的结构

3.1 HTTP请求

HTTP请求

3.2 HTTPBasicAuth认证方式

通过Vscode restClient模仿不同的rest请求


@password=0ebaa294-cc23-45df-977c-ebbe40e12123

### Get请求

GET http://localhost:8080/api/greeting HTTP/1.1

### 带BasicAuth 的get认证请求

GET http://localhost:8080/api/greeting HTTP/1.1

Authorization: Basic user {{password}}

### 带QueryParam 查询参数的POST请求

POST http://localhost:8080/api/greeting?name=王五

Authorization: Basic user {{password}}

Content-Type: application/json

  

{

"gender": "男",

"idNo": "22323232323"

}

### 路径参数

PUT http://localhost:8080/api/greeting/王五

Authorization: Basic user {{password}}

###

POST http://localhost:8080/authorize/login

Content-Type: application/json

  

{

"username": "user",

"password": "1234567"

}

4 HTTP响应和HTTP Basic Auth

4.1 HTTP响应

一个典型的状态行:HTTP/1.1 201 Created。

  • General Headers 与每次响应无关,是全局的设置

  • Response Header 与每一个响应相关

  • Entity Headers

HTTP响应

4.2 HTTP Basic Auth认证流程

认证流程:

  • 客户端发送对某个资源(URL)的请求

  • 服务器会返回一个401表示未授权,并在响应里添加WWW-Authenticate:Basic realm="Access to the staging site"

  • 浏览器收到响应后会重定向到登录表单页面。或者浏览器弹出一个窗口让你填写用户名和密码。

  • 当填写了密码后,浏览器会将密码进行Basic64编码

  • 客户端接收信息后对用户名密码进行验证。

  • 验证成功返回自定资源

HTTP base Auth 是表单验证的一种方式.

HTTP Base Auth

5 安全配置

5.1 HttpSecurity安全配置

配置 Spring Security

WebSecurityConfigurerAdapter 中的configure(HttpSecurity)

安全配置主要分为:

  1. 认证请求配置 authorizeRequests()

  2. 表单登录部分 formLogin()

  3. 表单认证 httpBasic()


protected  void  configure(HttpSecurity http) throws Exception {

logger.debug  ("Using default configure(HttpSecurity). If subclas");

http

.authorizeRequests()

.anyRequest().authenticated()  //任何请求都会进行认证

.and()

.formLogin()  //启用内建的登录界面.and ()

.httpBasic();  //使用HTTP Basic Auth 进行认证

}


protected  void  configure(HttpSecurity http) throws Exception {

http

.authorizeRequests(authorizeRequests -> authorizeRequests

.antMatchers("/authorize/**").permitAll()

.antMatchers("/admin/**").hasRole("ADMIN")

.antMatchers("/api/**").hasRole("USER")

.anyRequest().authenticated())

.addFilterAt(restAuthenticationFilter(),  UsernamePasswordAuthenticationFilter.class)

.formLogin(login -> login

.loginPage("/login")

//.usernameParameter("username1") //表单登录参数 默认为username

//.loginProcessingUrl("/login1") //表单action参数 默认为login

//.rememberMe(rememberMe -> rememberMe.rememberMeParameter)

.failureHandler(jsonLoginFailureHandler())

.successHandler(new  UaaSuccessHandler())

// .successHandler(jsonLoginSuccessHandler())

// .defaultSuccessUrl("/")

.permitAll())

.logout(logout -> logout

.logoutUrl("/perform_logout")

// .logoutSuccessUrl("/login")

.logoutSuccessHandler(jsonLogoutSuccessHandler())

)

.rememberMe(rememberMe -> rememberMe

.key("someSecret")

.tokenValiditySeconds(86400))

.httpBasic(Customizer.withDefaults());  // 显示浏览器对话框,需要禁用 CSRF ,或添加路径到忽略列表

}

rememberMe()见

.httpBasic(Customizer.withDefaults())启用了HTTPBasic,我们可以有一个认证头

5.2 application.yml 配置默认用户


spring:

messages:

basename: messages

encoding: UTF-8

security:

user:

name: user

password:  12345678

roles: USER,ADMIN

5.3 WebSecurity安全配置

配置静态文件地址、在HTTPSecurity过滤器前面。将一些指定地址的资源,和常见的公共资源放开。


@Override

public  void  configure(WebSecurity web) throws Exception {

web

.ignoring()

.antMatchers("/public/**")

.requestMatchers(PathRequest.toStaticResources().atCommonLocations());

}

6 自定义登录页

6.1 SpringResourceBundle国际化

因为有很多文本信息,所以需要建立一个resourceBundle处理国际化。

Spring新建国际化包:

resource -> New -> resource bundle

国际化

国际化配置文件:

初始化国际化配置文件

配置 application.yml

basename和新建时填写的basename保持一致


spring:

messages:

always-use-message-format:  false

basename: message

encoding: UTF-8

fallback-to-system-locale:  true

use-code-as-default-message:  false

6.2 国际化的参数和使用

6.2.1 前台参数


  

###

# @name register

POST {{host}}/authorize/register

Accept-Language: zh-CN

Content-Type: application/json

  

{

"name": "张三李四",

"username": "zhangsan",

"password": "qwerty12345T!",

"matchingPassword": "12345678",

"email": "zs@local"

}

6.2.2 后台接收


@PostMapping("/register")

public  void  register(@Valid  @RequestBody  UserDto userDto,  Locale locale)  {

if  (userService.isUsernameExisted(userDto.getUsername()))  {

throw  new  DuplicateProblem("Exception.duplicate.username", messageSource, locale);

}

if  (userService.isEmailExisted(userDto.getEmail()))  {

throw  new  DuplicateProblem("Exception.duplicate.email", messageSource, locale);

}

if  (userService.isMobileExisted(userDto.getMobile()))  {

throw  new  DuplicateProblem("Exception.duplicate.mobile", messageSource, locale);

}

val user =  User.builder()

.username(userDto.getUsername())

.name(userDto.getName())

.email(userDto.getEmail())

.mobile(userDto.getMobile())

.password(userDto.getPassword())

.build();

userService.register(user);

}

7 csrf,logout和rememberMe的设置

7.1 CSRF攻击是什么?

你已经登录了一个站点,站点的登录状态有session,并且你的session还在有效期之内,恶意用户会发一个恶意链接,盗取你的账户名和密码生成一个表单信息,向真正的站点发送提交。

CSRF攻击

7.2 防止受到CSRF攻击的方式

7.2.1 CSRF Token方式

  1. 由服务端生成并设置一个CSRF token到浏览器前端Cookie中

  2. 前端读取Cookie的CSRF token添加到表单中。

  3. 后台读取请求后验证CSRF token是否一致。

CSRF Token

7.2.2 在响应中设置Cookie的SameSite属性

SameSite

无状态访问的请求对CSRF攻击天然免疫。

7.3 Remember-me功能

为解决session过期后用户的直接访问问题

Spring Security 提供开箱即用的配置rememberMe

原理∶使用Cookie存储用户名,过期时间,以及一个 Hash

Hash : md5(用户名+过期时间+密码+key)

8. 登录成功及失败的处理

8.1.定制登录/退出登录的处理

定制登录/退出登录的处理。即将返回值结果以指定的JSON格式返回。方便无状态应用的使用.

登录成功后的处理:AuthenticationSuccessHandler


private  AuthenticationSuccessHandler  jsonLoginSuccessHandler()  {

return  (req, res, auth)  ->  {

ObjectMapper  objectMapper  =  new  ObjectMapper();

res.setStatus(HttpStatus.OK.value());

res.getWriter().println(objectMapper.writeValueAsString(auth));

log.debug("认证成功");

};

}

登录失败后的处理:AuthenticationFailureHandler


private  AuthenticationFailureHandler  jsonLoginFailureHandler()  {

return  (req, res, exp)  ->  {

res.setStatus(HttpStatus.UNAUTHORIZED.value());

res.setContentType(MediaType.APPLICATION_JSON_VALUE);

res.setCharacterEncoding("UTF-8");

val errData =  Map.of(

"title",  "认证失败",

"details",  exp.getMessage()

);

res.getWriter().println(objectMapper.writeValueAsString(errData));

};

}

退出登录成功后的处理:LogoutSuccessHandler


private  LogoutSuccessHandler  jsonLogoutSuccessHandler()  {

return  (req, res, auth)  ->  {

if  (auth !=  null  &&  auth.getDetails()  !=  null)  {

req.getSession().invalidate();

}

res.setStatus(HttpStatus.OK.value());

res.getWriter().println();

log.debug("成功退出登录");

};

}

9 自定义Filter

要求会自定义一个Filter,实现对用户请求的拦截和数据的解析,并将其加入到SpringSecurity的过滤器链里。

Spring推荐使用构造函数进行注入。 自定义一个用户名密码授权过滤器。

9.1 自定义filter


@RequiredArgsConstructor

public  class  RestAuthenticationFilter  extends  UsernamePasswordAuthenticationFilter  {

  

private  final  ObjectMapper  objectMapper;

  

@Override

public  Authentication  attemptAuthentication(HttpServletRequest  request,  HttpServletResponse  response)  throws  AuthenticationException  {

UsernamePasswordAuthenticationToken  authRequest;

try  (InputStream  is  =  request.getInputStream())  {

val jsonNode =  objectMapper.readTree(is);

  

String  username  =  jsonNode.get("username").textValue();

String  password  =  jsonNode.get("password").textValue();

authRequest =  new  UsernamePasswordAuthenticationToken(username, password);

}  catch  (IOException  e)  {

throw  new  BadCredentialsException("没有找到用户名或密码参数");

}

setDetails(request, authRequest);

return  this.getAuthenticationManager().authenticate(authRequest);

}

}

9.2 实例化过滤器链

  • 实例化过滤器链

  • 设置成功和失败的处理器

  • 设置authenticationManager()

  • 设置于某个应用的URL


private  RestAuthenticationFilter  restAuthenticationFilter() throws Exception {

RestAuthenticationFilter  filter  =  new  RestAuthenticationFilter(objectMapper);

filter.setAuthenticationSuccessHandler(jsonLoginSuccessHandler());

filter.setAuthenticationFailureHandler(jsonLoginFailureHandler());

filter.setAuthenticationManager(authenticationManager());

filter.setFilterProcessesUrl("/authorize/login");

return filter;

}

9.3 将过滤器链替代指定的过滤器链

可以在指定的过滤器链之前或之后添加过滤器链,也可以替换该过滤器链。

.addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消