前一段时间写了有关登陆的功能。包括Springboot security的简单使用,微信扫码登陆,接入spring cloud 的微信登陆。本文就有关Springboot security的登陆做一些简单记录。
项目地址见文末
Springboot security登陆
关于登陆功能的实现采取的是Basic64
传输登陆信息,token
认证。本文以此为例。
用户登陆就是服务器接收到用户输入的用户名和密码然后与数据库中的用户名和密码比对
。成功,则颁发X-auth-token
,失败则返回401
而通过Springboot Security去实现登陆功能,可以实现登陆与认证的自动化。
实现流程
- 用户在浏览器中输入用户名密码进行登陆,该信息通过Base64编码之后放到请求头并发起请求。
- 后台接收到相关请求之后调用
Basic64
的解码方式进行解码并获取到用户名和密码信息。 - 调用
loadUserByUsername()
方法获取相应用户实体。 - 获取用户实体之后将该用户请求中的密码和实体的密码进行匹配。
- 匹配成功:颁发token,前台进行相关跳转;匹配失败:返回
401
。
注:登录流程的实现由过滤器完成
Springboot security配置项
通过图示,可以看出我们需要告诉Springboot security
解码方式(Basic64
),如何获取用户信息,如何比对密码(PasswordEncoder
),后续通信如何进行身份认证(X-auth-token
)
其中①解码方式,②密码比对方式,③身份认证方式 在项目中由配置类设定。④如何获取用户信息则由实现接口方法完成。
配置文件
在config
文件夹中创建一个配置类MvcSecurityConfig
,内容如下。
@Configuration
@EnableWebSecurity
@EnableSpringHttpSession
public class MvcSecurityConfig extends WebSecurityConfigurerAdapter {
private final BCryptPasswordEncoder passwordEncoder;
public MvcSecurityConfig() {
this.passwordEncoder = new BCryptPasswordEncoder();
User.setPasswordEncoder(this.passwordEncoder);
}
/**
* https://spring.io/guides/gs/securing-web/
*
* @param http http安全
* @throws Exception 异常
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// 开放端口
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/wechat/**").permitAll()
.antMatchers("/websocket/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.and().cors()
.and().csrf().disable();
http.headers().frameOptions().disable();
}
@Bean
PasswordEncoder passwordEncoder() {
return this.passwordEncoder;
}
@Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderAndParamHttpSessionStrategy();
}
@Bean
public SessionRepository sessionRepository() {
return new MapSessionRepository();
}
}
①解码方式,②密码比对方式,③身份认证方式分别对应其中的httpBasic()
, passwordEncoder()
, httpSessionStrategy()
。
而 ④如何获取用户信息 则在UserService
中实现UserDetailsService
接口来规定。
①解码方式 即告诉后台前台发送请求时的basic
,让后台用basic
解码。
②密码比对方式 定义了如何对传来的明文密码进行加密,加密后的密码才可以存进数据库,同时在配置类的构造方法中对跟用户相关的实体User
的passwordEncoder
进行了设置,在调用user
实体的setPassword
方法时会先对密码进行加密之后再设置password
属性。
public void setPassword(String password) {
if (User.passwordEncoder == null) {
throw new RuntimeException("未设置User实体的passwordEncoder,请调用set方法设置");
}
this.password = User.passwordEncoder.encode(password);
}
③身份认证方式 由自定义类实现接口HttpSessionStrategy
规定认证以x-auth-token
作为关键字来发放凭证。
④如何获取用户信息 即通过数据库获取,见UserService
中loadUserByUsername()
实现:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = this.userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
// 设置用户角色
List authorities = new ArrayList<>();
return new org.springframework.security.core.userdetails.User(username, user.getPassword(), authorities);
}
这样一个简单的登录过程就实现了,
另外,该实践中设置了MapSessionRepository
来管理会话,实际上不自己设置也是可以的。
问题
实际上以上这种写法存在一个问题,就是已登录用户在未注销的情况下返回到登录页面然后输入一个错误的密码,也能够成功进入系统。
原因是已登录用户本身有一个可以通过系统认证的x-auth-token
,此时用户调用登录接口,后端发现x-auth-token
存在并且可以使用,便对接收到的登录请求放行。而在后端的登录接口并未验证,最终登录成功。
在login
方法中添加认证检查可以解决,具体可见代码的login()
,需要注意的是为了实现手动认证,需要在Spring Security
的配置类中添加如下代码在项目中来注入Bean
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}