欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > 一文上手SpringSecurity【四】

一文上手SpringSecurity【四】

2025/5/7 3:50:47 来源:https://blog.csdn.net/ldcigame/article/details/142584513  浏览:    关键词:一文上手SpringSecurity【四】

在上一篇当中我们自定义了认证的登录页面,定了一个基本的表格,自定义了登录的密码,但是咱们都知道不能让所有用户都使用一个密码啊…

一、自定义认证登录页面细节分析

1.1 默认登录页面html页面详情

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><meta name="description" content=""><meta name="author" content=""><title>Please sign in</title><link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"><link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" integrity="sha384-oOE/3m0LUMPub4kaC09mrdEhIc+e3exm4xOGxAmuFXhBNF4hcg/6MiAXAf5p0P56" crossorigin="anonymous"/></head><body><div class="container"><form class="form-signin" method="post" action="/login"><h2 class="form-signin-heading">Please sign in</h2><p><label for="username" class="sr-only">Username</label><input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus></p><p><label for="password" class="sr-only">Password</label><input type="password" id="password" name="password" class="form-control" placeholder="Password" required></p>
<input name="_csrf" type="hidden" value="WUrEFQvTxRrns-H-eiOdlycETF2qYMfVJ4ww3X5ccJWZmU97Py72JGrl9S_Kh9ScHg6poRA0YWWbA_f4FrlT7xo_E6P8oCxJ" /><button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button></form>
</div>
</body></html>

以上是生成的默认的登录页面html源码,我们只看核心部分

     <form class="form-signin" method="post" action="/login"><h2 class="form-signin-heading">Please sign in</h2><p><label for="username" class="sr-only">Username</label><input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus></p><p><label for="password" class="sr-only">Password</label><input type="password" id="password" name="password" class="form-control" placeholder="Password" required></p>
<input name="_csrf" type="hidden" value="WUrEFQvTxRrns-H-eiOdlycETF2qYMfVJ4ww3X5ccJWZmU97Py72JGrl9S_Kh9ScHg6poRA0YWWbA_f4FrlT7xo_E6P8oCxJ" /><button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button></form>

这里需要注意的是:

  • 请求方式必须是post
  • 用户名称的参数默认必须是: username
  • 密码的参数默认必须是: password

1
我们知道,所有的认证的时候,会执行UsernamePasswordAuthenticationFilter过滤器, 其中在UsernamePasswordAuthenticationFilter#attemptAuthentication方法当中

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {// 判断一下当前的认证请求是不是post,不是就抛出异常if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}// 从请求参数当中取出用户名称String username = obtainUsername(request);username = (username != null) ? username.trim() : "";// 从请求参数当中取出密码String password = obtainPassword(request);password = (password != null) ? password : "";UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);
}

String username = obtainUsername(request);
String password = obtainPassword(request);

@Nullable
protected String obtainUsername(HttpServletRequest request) {return request.getParameter(this.usernameParameter);
}@Nullable
protected String obtainPassword(HttpServletRequest request) {return request.getParameter(this.passwordParameter);
}

找到this.usernameParameter和this.passwordParameter

private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

可以发现,这两上参数默认就是: username和passwork, 如果想要修改,则可以在配置类当中进行修改.
默认的UsernamePasswordAuthenticationFilter处理请求就是/login,在类当中定义了变量

private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login","POST");

调用父类AbstractAuthenticationProcessingFilter构造方法的时候, 将其传递给父类了

protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher cannot be null");// 给父类的成员变量赋值this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;}

判断当前登录的请求是不是/login

protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {if (this.requiresAuthenticationRequestMatcher.matches(request)) {return true;}if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Did not match request to %s", this.requiresAuthenticationRequestMatcher));}return false;
}

该方法,在doFilter里进行调用,对登录请求进行拦截,如果不是/login,则直接跳过当前的过滤器

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {// 判断一下是不是当前过滤器能处理的请求,如果不是,则跳过当前过滤器的执行,继续下一个过滤器的执行if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}// 略

如果要修改请求的uri,则可以在配置类当中进行配置

 @Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {returnhttp.authorizeHttpRequests(authorize -> {// 如果请求的是/login.html则放行authorize.requestMatchers("/login.html").permitAll().anyRequest().authenticated(); // 其它的请求都需要认证}).formLogin(form -> { // 配置登录相关的信息// 用来指定默认的登录页面, 注意: 一旦自定义登录页面以后必须配置一下登录的url【必须】form.loginPage("/login.html")// 自定义参数名称为yonghuming和mima.usernameParameter("yonghuming").passwordParameter("mima")// 自定义登录的url.loginProcessingUrl("/dologin"); // ①. 指定处理登录的url【必须的】}).csrf(AbstractHttpConfigurer::disable).build();}

2
认证成功
3

1.2 FormLoginConfigurer

在自定义配置类当中,FormLoginConfigurer是一个用于配置表单登录功能的类.在配置类当中,主要使用的就是它

public HttpSecurity formLogin(Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer) throws Exception {formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>()));return HttpSecurity.this;
}
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extendsAbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
}

其核心方法如下所示

  • loginPage(String loginPage):
    用于设置自定义的登录页面 URL。如果不设置,Spring Security 会使用默认的登录页面。
  • loginProcessingUrl(String loginProcessingUrl):
    指定登录处理的 URL。当用户提交登录表单时,请求会被发送到这个 URL 进行处理。
  • usernameParameter(String usernameParameter)和passwordParameter(String passwordParameter):
    分别用于设置用户名和密码在登录表单中的参数名称。默认情况下,用户名字段为 “username”,密码字段为 “password”。
  • defaultSuccessUrl(String defaultSuccessUrl, boolean alwaysUse):
    设置登录成功后的默认重定向 URL。alwaysUse参数决定是否总是使用这个 URL 进行重定向,即使之前有保存的请求(如在登录前试图访问一个受保护的资源)。
  • failureUrl(String failureUrl):
    指定登录失败后的重定向 URL。
  • permitAll():
    配置登录页面和登录处理 URL 可以被所有用户访问,即使没有经过身份验证。
  • and():
    用于连接配置,返回父类HttpSecurity对象以便继续进行其他安全配置。

以上可以根据自己的需求,进行选择性的进行配置.

二、自定义数据源

2.1 自定义数据源分析

当前我们将用户名称和密码配置在了application.yml文件当中了,但是不能所有用户都使用一个账号和密码啊啊啊啊.再者,先前所有的账号密码都存储在了内存当中,应用重新就啥也没有了,以我们的经验来说,用户账号信息等数据肯定需要持久化存储的.

根据上一篇当中认证流程分析的流程图解
4
回顾一下这里的核心逻辑:

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

this.getUserDetailsService()返回的是UserDetailsService接口,实际上这里返回的是InMemoryUserDetailsManager对象,因为在程序启动的时候, 已经将InMemoryUserDetailsManager放到容器里了.

在UserDetailsServiceAutoConfiguration配置类当中, 可以看到该生效的条件当中

@AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class)
@Conditional(MissingAlternativeOrUserPropertiesConfigured.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
public class UserDetailsServiceAutoConfiguration {}

其中我们看

@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,AuthenticationManagerResolver.class }

@ConditionalOnMissingBean表示如果在容器当中, 没有AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,AuthenticationManagerResolver.class几个对象,则配置类生效, 反之,如果我们要自己定义的数据源, 不再采用默认的基于内存存储,则可以直接将它们其中的任意一个对象,放到容器里去即可.这里我们根据业务选择【自己去实现UserDetailsService即可,重写抽象方法】.

2.2 参考示例

在项目当中,直接定义UserDetailsService接口的实现类即可,这里我们先模拟一下操作即可

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 模拟数据库中查询到的用户名称和密码String name = "rj";String password = "rj";// 判断一下用户名称和密码是否正确if(!name.equals(username)){throw new UsernameNotFoundException("用户名或密码错误");}// 如果用户名称正确,则去数据源当中,根据用户名称取出相应的用户数据.// 这里模拟一下操作,即可.后续再添加上数据源,我们这里暂定使用MySQL// 将取出的用户数据,封装成 UserDetails 对象返回即可// 结合之前的分析,User是 UserDetails 的子类,所以直接返回即可// 参数说明// 1. 用户名// 2. 密码// 3. 角色、权限列表return new User(username, password, Collections.emptyList());}
}

自定义数据源就是这么简单.

  • 注意事项
    • 示例代码仅仅是模拟操作
    • User不要导错包,它org.springframework.security.core.userdetails包下,切记.
    • User的构造方法,最后一个表示角色、权限列表目前我们没有步及,这里传入了一个空的集合

2.3 验证

启动服务, 访问/hello接口,输入我默认的用户名称和密码,点击提交
6
9

到此,自定义数据源完成,总体来说,理解了整个认证流程,理解起来不是很难.

三、总结

3.1 重点总结

  • 对于认证登录细节分析
  • 自定义数据源,即UserDetailsService, 其它的【核心业务】依然是由SpringSecurity帮助我们完成的, 如下图所示,我们只是开了个头,然后提供了一下认证数据而已
    67
  • 以上的自定义认证的登录页面、数据源只适用于前后端不分离的情况下使用

3.2 下篇内容

  • 前后端分离的情况下自定义认证流程

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词