LeeQingShui's Blog

  • 标签

  • 分类

  • 归档

  • 关于

JavaScript 复习笔记

发表于 2017-04-25 | 更新于 2022-06-21 | 分类于 前端
本文字数: 14k | 阅读时长 ≈ 20 分钟

什么是 JavaScript ?

  JavaScript(通常缩写为 JS )是一种高级的、解释型的编程语言[5]。JavaScript 是一门基于原型、函数先行的语言[6],是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。它提供语法来操控文本、数组、日期以及正则表达式等,不支持 I/O ,比如网络、存储和图形等,但这些都可以由它的宿主环境提供支持。它已经由 ECMA(欧洲计算机制造商协会)通过 ECMAScript 实现语言的标准化[5]。它被世界上的绝大多数网站所使用,也被世界主流浏览器( Chrome、IE、Firefox、Safari、Opera )支持。

  虽然 JavaScript 与 Java 这门语言不管是在名字上,或是在语法上都有很多相似性,但这两门编程语言从设计之初就有很大的不同,JavaScript 的语言设计主要受到了 Self(一种基于原型的编程语言)和 Scheme(一门函数式编程语言)的影响[6]。在语法结构上它又与C语言有很多相似(例如 if 条件语句、while 循环、switch 语句、do-while 循环等)[7]。

  在客户端,JavaScript 在传统意义上被实现为一种解释语言,但现在它已经可以被即时编译( JIT )执行。随着最新的 HTML5 和 CSS3 语言标准的推行它还可用于游戏、桌面和移动应用程序的开发和在服务器端网络环境运行,如 Node.js 。

阅读全文 »

CSS 复习笔记

发表于 2017-04-25 | 更新于 2022-10-22 | 分类于 前端
本文字数: 8.8k | 阅读时长 ≈ 13 分钟

  CSS(Cascading Style Sheets),即层叠样式表,是一种用来为结构化文档(如 HTML 文档或 XML 应用)添加样式(字体、间距和颜色等)的计算机语言,由 W3C 定义和维护。

阅读全文 »

(二)Spring Security 认证原理

发表于 2011-10-24 | 更新于 2022-10-22 | 分类于 信息安全
本文字数: 3.3k | 阅读时长 ≈ 5 分钟

序言

  在本系列文章(一)初识 Spring Security 中,我们不仅初步认识了 Spring Security,明晰了 Spring Security 的三个核心概念——认证、鉴权、防护,还准备研究其工作原理。

  现在,就跟随本篇文章来探讨认证与授权是如何实现的吧。

认证与授权

  在 Security 中,认证和授权功能是交由过滤器UsernamePasswordAuthenticationFilter来完成的。
  那么,UsernamePasswordAuthenticationFilter又是如何运作的呢?

  我们直接去阅读源码,最终将得到简化的认证授权流程信息:

  • ① UsernamePasswordAuthenticationFilter执行attemptAuthentication方法将提交的表单信息封装为一个UsernamePasswordAuthenticationToken对象,传递给AuthenticationManager执行authenticate认证方法
  • ② AuthenticationManager接口通过多态选择默认的实现类ProviderManager去执行authenticate方法
  • ③ ProviderManager在执行该方法时会遍历AuthenticationProvider列表,通过多态选择实现类AbstractUserDetailsAuthenticationProvider和DaoAuthenticationProvider去执行认证方法
  • ④ DaoAuthenticationProvider在retrieveUser方法中会从UserDetailsService的实现类的loadUserByUsername得到UserDetails对象
  • ⑤ 在校验UserDetails对象的合理性之后,创建一个认证成功的存放了权限信息的Authentication对象,用作后续的鉴权

  现在你肯定看的一头雾水,因为你并不知道上面流程中的各个接口和类的作用是什么,不过没关系,下面我们将一步一步地分析它们。

UsernamePasswordAuthenticationToken

  在认证流程的第 ① 步中会将表单信息封装UsernamePasswordAuthenticationToken该对象,这发生在UsernamePasswordAuthenticationFilter的attemptAuthentication方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 1.获取表单中的用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);

if (username == null) {
username = "";
}

if (password == null) {
password = "";
}

username = username.trim();
// 2.封装为一个对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);

// Allow subclasses to set the "details" property
setDetails(request, authRequest);

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

  这个对象是干啥的呢?我们看下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
...
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
...
}

public abstract class AbstractAuthenticationToken implements Authentication,
CredentialsContainer {

// 权限集合
private final Collection<GrantedAuthority> authorities;
private Object details;
private boolean authenticated = false;
...
}

  从源码中得知:

  • principal:等同于username
  • credentials:等同于password
  • authorities:继承的父类权限集合暂时为空

AuthenticationManager

  构造完UsernamePasswordAuthenticationToken对象后应当做些什么?
  那肯定是查看数据库是否存在与UsernamePasswordAuthenticationToken对象匹配的用户名,没有肯定就认证失败了嘛!

  认证具体是交由AuthenticationManager接口的实现类ProviderManager去完成的,我们来看下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 认证提供者列表
private List<AuthenticationProvider> providers = Collections.emptyList();

public List<AuthenticationProvider> getProviders() {
return providers;
}

public Authentication authenticate(Authentication authentication)
throws AuthenticationException {

for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
...

try {
// 根据不同的认证提供者进行认证
result = provider.authenticate(authentication);
// 只要有一个认证提供者认证成功,就算认证成功,跳出循环
if (result != null) {
copyDetails(authentication, result);
break;
}
...
} catch {
...
}
}

if (result != null) {
...
return result;
}
}

  ProviderManager维护了一个 List,该 List 中存放了AuthenticationProvider接口不同的实现类,认证时需遍历所有的实现类进行认证,只要有一个AuthenticationProvider认证成功,就算认证成功。

  一般情况下,是由AuthenticationProvider接口的实现类DaoAuthenticationProvider根据用户名从数据源中获取用户信息对象的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {

prepareTimingAttackProtection();
try {
// 从数据源中根据用户名获取 UserDetails 对象
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
} catch (){
...
}
}

  获取到UserDetails对象后,就是密码的比对了,由于注册时密码应当会以特定算法加密密码用密文进行保存,所以解密时也需要先通过对应的加密算法加密为密文,与获取的密文进行匹配,匹配成功,则认证成功。
  以上过程由additionalAuthenticationChecks方法完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
// 表单未输密码抛异常
throw new BadCredentialsException(...);
}

String presentedPassword = authentication.getCredentials().toString();

// 将表单密码加密后与数据源获取的密文进行匹配
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
// 不匹配抛异常
throw new BadCredentialsException(...);
}
}

扩展——ProviderManager 为何维护了一个 AuthenticationProvider 列表?

  在前面的分析过程中,我们发现ProviderManager类中维护了一个AuthenticationProvider列表,为什么需要这个列表呢?
  定义AuthenticationProvider接口的目的在于认证方式可能不同,需要子类实现,默认情况下的认证方式实现类为DaoAuthenticationProvider。
  emmm,认证方式,什么意思?
  我们知道现在表单使用的是用户名/密码的信息,所以我们数据源查询应当是根据用户名查询出用户对象,但如果产品提了需求说再加一种认证方法,使用电话和验证码的方式进行认证,那现在的认证方式不就不符合业务需要了嘛?
  此时,我们可以自定义一个AuthenticationProvider的实现类,重写业务逻辑以匹配业务场景。

  因此,总结来讲就是会存在五花八门的认证方式:

  • 用户名/密码
  • 邮箱/密码
  • 手机号/验证码
  • 人脸识别

  因此,若默认的用户名/密码认证方式不符合业务需要,可以随业务进行扩展。

UserDetailsService

  不得不提一嘴,若没有任何配置,Spring Security 根据用户名查询用户对象的数据源是从默认的内存中读取的,这自然不符合正常场景,我们的用户信息都是保存到数据库的,那么如何进行修改以从数据库中查询用户对象呢?

  从前面知道,根据用户名获取用户信息的过程是由前面的DaoAuthenticationProvider中调用的retrieveUser方法的以下方法实现:

1
2
// 从数据源中根据用户名获取 UserDetails 对象
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

  我们从源码来看看这个UserDetailsService到底是什么:

1
2
3
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

  很明显,它是一个接口,默认根据用户名读取用户信息的实现类InMemoryUserDetailsManager重写了它的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class InMemoryUserDetailsManager implements UserDetailsManager,
UserDetailsPasswordService {
......

public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails user = users.get(username.toLowerCase());

if (user == null) {
throw new UsernameNotFoundException(username);
}

return new User(user.getUsername(), user.getPassword(), user.isEnabled(),
user.isAccountNonExpired(), user.isCredentialsNonExpired(),
user.isAccountNonLocked(), user.getAuthorities());
}
......
}

  那么,我们现在想走数据库去查询用户信息,肯定就需要实现该接口,重写其方法,从数据库读取用户信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Service
public class UserServiceImpl implements UserDetailsService {

@Autowired
private PasswordEncoder passwordEncoder;

@Autowired
private UserMapper userMapper;

/**
* Spring Security 认证授权均通过 UserDetailsService 接口提供的 loadUserByUsername 方法,
* 该方法返回 UserDetails 对象,该对象包含了用户角色权限信息,之后 url 权限校验需要使用
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> userWrapper = new QueryWrapper<>();
userWrapper.eq("username", username);
User user = userMapper.selectOne(userWrapper);
if (user == null) {
throw new UsernameNotFoundException(username);
}
Set<String> permissionSet = userMapper.selectPermissionListByUsername(username);

return new SecurityUser(user.getUsername(), user.getPassword(), permissionSet,
true, true, true, true);
}
}

  定义完成以后,还需要将默认的实现类进行替换,直接注入即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@EnableWebSecurity 
public class SecurityConfig extends WebSecurityConfigurerAdapter {

......

@Autowired
private UserServiceImpl userService;

/**
* 认证:使用自定义的 UserDetailsService 实现类读取用户信息
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
......
}

UserDetails

  不知道你有没有注意到一点,loadUserByUsername方法的返回值为UserDetails接口,其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface UserDetails extends Serializable {

// 权限集合
Collection<? extends GrantedAuthority> getAuthorities();

// 获取密码
String getPassword();

// 获取用户名
String getUsername();

// 帐号未过期
boolean isAccountNonExpired();

// 帐号未锁定
boolean isAccountNonLocked();

// 凭证未过期
boolean isCredentialsNonExpired();

// 帐号可用
boolean isEnabled();
}

  咦,这方法不是应该从数据库查询出用户对象嘛?为什么返回这个接口呢?什么意思?

  emmm,这方法准确而言,应当从数据库查询出一个包含角色和权限的用户对象,因为后续鉴权需要使用到,并且由于 Spring Security 后续鉴权都是通过UserDetails接口多态进行判断的,所以我们最好创建一个对象实现该接口,然后根据数据库信息填充该对象,不然是不符合 Security 的要求滴!

PasswordEncoder

  前面说过,数据库的密码是加密后的密文形式,所以比对时需要将表单的密码进行加密,之后才能判断它们是否相等,表单密码的加密就需要用到PasswordEncoder了:

1
2
3
4
5
6
7
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");

throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}

参考

  • Spring Security 官网

(一)初识 SpringSecurity

发表于 2011-10-23 | 更新于 2025-12-01 | 分类于 信息安全
本文字数: 4.7k | 阅读时长 ≈ 7 分钟

序言

  应用程序的安全性通常体现在三个方面:

  • 认证:认证是确认某主体在某系统中是否合法、可用的过程。这里的主体既可以是登录系统的用户,也可以是接入的设备或者其他系统
  • 授鉴权: 授权是指当主体通过认证之后,赋予主体对应权限,鉴权是判断是否允许主体执行某项操作的过程
  • 防护:防护是指系统对不法攻击的抵御能力

  作为 Java 开发,要想使应用具备以上特性,你当然可以自己实现一套框架,但是技术发展这么多年,市面上应当已存在了成熟的框架,我们又为何要自己去造轮子呢?

  本文将带你了解 Spring Security,通过它将使我们更便捷地实现安全性功能。

阅读全文 »

Ops

发表于 1024-10-24 | 更新于 2025-12-04 | 分类于 Ops
本文字数: 213 | 阅读时长 ≈ 1 分钟

反向链接

  • [[01.Linux_复习笔记]]
  • [[02.Centos_7_防火墙]]
  • [[03.初识_Shell]]
  • [[06.Nginx_总结]]
  • [[07.一览无余:妙用 Linux 抓包工具 tcpdump 排查网络问题]]
  • [[11.须弥芥子:容器隔离技术——Docker]]
  • [[66.洞幽察微:Prometheus + Grafana 打造实时全链路监控系统]]
  • [[69.北门管钥:内网穿透利器——Frp]]

未命名

发表于 1024-10-24 | 更新于 2024-02-08
本文字数: 4 | 阅读时长 ≈ 1 分钟

反向链接

未命名

发表于 1024-10-24 | 更新于 2024-02-08
本文字数: 227 | 阅读时长 ≈ 1 分钟

反向链接

  • [[20.(一)初识_ElasticSearch]]
  • [[21.(二)ElasticSearch_检索技术——DSL]]
  • [[22.(三)ElasticSearch_数据纵览——聚合]]
  • [[23.(四)ElasticSearch_集群]]
  • [[24.(五)ElasticSearch_分片深入研究]]
  • [[25.(六)ElasticSearch_集群分片未分配问题探究及解决方案]]
  • [[30.(X)ElasticSearch 优化篇]]

前端

发表于 1024-10-24 | 更新于 2024-02-08
本文字数: 189 | 阅读时长 ≈ 1 分钟

反向链接

  • [[01.CSS 复习笔记]]
  • [[02. JavaScript 复习笔记]]
  • [[10.(一)初识 Vue]]
  • [[11.(二)Vue 的组件化开发]]
  • [[12.(三)Vue 项目的快速搭建]]
  • [[13.(四)Vue 路由—— Vue Router]]
  • [[14.(五)Vue 状态管理—— Vuex]]
  • [[15.(六)Vue 网络通信—— Axios]]

未命名

发表于 1024-10-24 | 更新于 2024-02-08
本文字数: 4 | 阅读时长 ≈ 1 分钟

反向链接

tool

发表于 1024-10-24 | 更新于 2025-10-13
本文字数: 149 | 阅读时长 ≈ 1 分钟

反向链接

  • [[00.Hexo 快速建站]]
  • [[01.初识_Maven]]
  • [[02.巧用Nexus搭建Maven私服]]
  • [[03.Arthas]]
  • [[11.OSS_Minio]]
  • [[60.CICD_GitLab]]
  • [[61.CICD_Jenkins]]
  • [[66.RustDesk]]
1…131415
LeeQingShui

LeeQingShui

147 日志
16 分类
67 标签
RSS
© 2018 – 2025 LeeQingShui | 站点总字数: 864k
赣 ICP 备 2022002212 号
本站已运行
本站总访问量 次 | 本站访客 人次
0%