Spring Security 玩出花!两种方式 DIY 登录

    • 1. 化腐朽为神奇
    • 1.1 实践
  • 1.2 原理分析

  • 2. 源码的力量

  • 3. 小结

一般情况下,我们在使用 Spring Security 的时候,用的是 Spring Security 自带的登录方案,配置一下登录接口,配置一下登录参数,再配置一下登录回调就能用了,这种用法可以算是最佳实践了!

但是!

总会有一些奇奇怪怪得需求,例如想自定义登录,像 Shiro 那样自己写登录逻辑,如果要实现这一点,该怎么做?今天松哥就来和大家分享一下。

松哥琢磨了一下,想在 Spring Security 中自定义登录逻辑,我们有两种思路,不过这两种思路底层实现其实异曲同工,我们一起来看下。

1. 化腐朽为神奇


前面松哥和大家分享了一个 Spring Security 视频:

这个视频里主要是和大家分享了我们其实可以使用 HttpServletRequest 来完成系统的登录,这其实是 JavaEE 的规范,这种登录方式虽然冷门,但是却很好玩!

然后松哥还和大家分享了一个视频:

这个视频其实是在讲 Spring Security 对 HttpServletRequest 登录逻辑的实现,或句话说,HttpServletRequest 中提供的那几个和登录相关的 API,Spring Security 都按照自己的实现方式对其进行了重写。

有了这两个储备知识后,第一个 DIY Spring Security 登录的方案呼之欲出。

1.1 实践

我们来看看具体操作。

首先我们来创建一个 Spring Boot 工程,引入 Web 和 Security 两个依赖,如下:

方便起见,我们在 application.properties 中配置一下默认的用户名密码:

spring.security.user.name=javaboy

spring.security.user.password=123

接下来我们提供一个 SecurityConfig,为登录接口放行:

@Configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

.antMatchers(“/login”)

.permitAll()

.anyRequest().authenticated()

.and()

.csrf().disable();

}

}

登录接口就是 /login,一会我们自定义的登录逻辑就写在这个里边,我们来看下:

@RestController

public class LoginController {

@PostMapping(“/login”)

public String login(String username, String password, HttpServletRequest req) {

try {

req.login(username, password);

return “success”;

} catch (ServletException e) {

e.printStackTrace();

}

return “failed”;

}

}

直接调用 HttpServletRequest#login 方法,传入用户名和密码完成登录操作。

最后我们再提供一个测试接口,如下:

@RestController

public class HelloController {

@GetMapping(“/hello”)

public String hello() {

return “hello security!”;

}

}

just this!

启动项目,我们首先访问 /hello 接口,会访问失败,接下来我们访问 /login 接口执行登录操作,如下:

登录成功之后,再去访问 /hello 接口,此时就可以访问成功了。

是不是很 Easy?登录成功后,以后的授权等操作都还是原来的写法不变。

1.2 原理分析

上面这种登录方式的原理其实松哥一开始就介绍过了,如果大家还不熟悉,可以看看这两个视频就懂了:

这里我也是稍微说两句。

我们在 LoginController#login 方法中所获取到的 HttpServletRequest 实例其实是 HttpServlet3RequestFactory 中的一个内部类 Servlet3SecurityContextHolderAwareRequestWrapper 的对象,在这个类中,重写了 HttpServletRequest 的 login 以及 authenticate 等方法,我们先来看看 login 方法,如下:

@Override

public void login(String username, String password) throws ServletException {

if (isAuthenticated()) {

throw new ServletException(“Cannot perform login for '” + username + “’ already authenticated as '”

  • getRemoteUser() + “'”);

}

AuthenticationManager authManager = HttpServlet3RequestFactory.this.authenticationManager;

if (authManager == null) {

HttpServlet3RequestFactory.this.logger.debug(

“authenticationManager is null, so allowing original HttpServletRequest to handle login”);

super.login(username, password);

return;

}

Authentication authentication = getAuthentication(authManager, username, password);

SecurityContextHolder.getContext().setAuthentication(authentication);

}

可以看到:

  1. 如果用户已经认证了,就抛出异常。

  2. 获取到一个 AuthenticationManager 对象。

  3. 调用 getAuthentication 方法完成登录,在该方法中,会根据用户名密码构建 UsernamePasswordAuthenticationToken 对象,然后调用 Authentication#authenticate 方法完成登录,具体代码如下:

private Authentication getAuthentication(AuthenticationManager authManager, String username, String password)

throws ServletException {

try {

return authManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));

}

catch (AuthenticationException ex) {

SecurityContextHolder.clearContext();

throw new ServletException(ex.getMessage(), ex);

}

}

该方法返回的是一个认证后的 Authentication 对象。

  1. 最后,将认证后的 Authentication 对象存入 SecurityContextHolder 中,这里的具体逻辑我就不啰嗦了,我在公众号【江南一点雨】之前的视频中已经讲过多次了。

这就是 login 方法的执行逻辑。

Servlet3SecurityContextHolderAwareRequestWrapper 类也重写了 HttpServletRequest#authenticate 方法,这个也是做认证的方法:

@Override

public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {

AuthenticationEntryPoint entryPoint = HttpServlet3RequestFactory.this.authenticationEntryPoint;

if (entryPoint == null) {

HttpServlet3RequestFactory.this.logger.debug(

“authenticationEntryPoint is null, so allowing original HttpServletRequest to handle authenticate”);

return super.authenticate(response);

}

if (isAuthenticated()) {

return true;

}

entryPoint.commence(this, response,

new AuthenticationCredentialsNotFoundException(“User is not Authenticated”));

最后

面试前一定少不了刷题,为了方便大家复习,我分享一波个人整理的面试大全宝典

  • Java核心知识整理

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

Java核心知识

  • Spring全家桶(实战系列)

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

  • 其他电子书资料

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

Step3:刷题

既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

以下是我私藏的面试题库:

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

  • Java核心知识整理

[外链图片转存中…(img-K4udlkmr-1718904235194)]

Java核心知识

  • Spring全家桶(实战系列)

[外链图片转存中…(img-Z7WJuGgj-1718904235195)]

  • 其他电子书资料

[外链图片转存中…(img-rjDzEORw-1718904235196)]

Step3:刷题

既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

以下是我私藏的面试题库:

[外链图片转存中…(img-NZKXnBkP-1718904235196)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值