项目场景:
公司开发了APP,不过因为开发的周期比较短。因此很多的功能都不是很完善。
比如用户的登录流程。之前的登录流程是直接通过调用接口,后端校验账号密码。校验成功之后直接返回用户信息。前端缓存用户信息,然后请求接口的时候把用户信息通过请求的数据带过来,用以标识当前接口的请求人。
感觉这么做并不安全,后端只能够根据前端传递的userId来区分用户标识。因此我打算基于spring security oauth来重构一下用户的登录模块,写这篇文章来记录自己从零到有的搭建过程以及排坑日记
技术介绍:
什么是OAuth2
OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该令牌在限定时间、限定范围访问指定资源。主要涉及的RFC规范有RFC6749(整体授权框架),RFC6750(令牌使用),RFC6819(威胁模型)这几个,一般我们需要了解的就是RFC6749。获取令牌的方式主要有四种,分别是授权码模式,简单模式,密码模式和客户端模式,如何获取token不在本篇文章的讨论范围,我们这里假定客户端已经通过某种方式获取到了access_token,想了解具体的oauth2授权步骤可以移步阮一峰老师的理解OAuth 2.0,里面有非常详细的说明。
而我这个项目使用的是密码模式(增强)以及自定义的手机验证码模式和第三方登录登录模式。
项目搭建:
最早开始我是阅读了《Spring Security实战》这本书,然后按照上面的步骤一步一步的来搭建.
首先第一步是引入依赖(idea构建项目就省略).
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-core</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<!-- 指明版本,解决redis存储出现的问题:java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V问题 -->
<version>2.3.3.RELEASE</version>
</dependency>
第二步配置相关信息
1.实现UserDetailsService和User
UserDetailsService在整个spring security相关重要,基本上所有的需要用户相关的都需要调用它。而User作为接口的返回值,如果你需要拓展它的话也可通过继承User。代码如下
@Slf4j
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
protected PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("认证开始,userName = {}",username);
// 获取用户信息以及角色省略,自己实现.
if (appUser == null)
throw new UsernameNotFoundException("用户信息查询异常.");
// 添加角色信息
Set<String> dbAuthSet = new HashSet<>();
dbAuthSet.add("ROLE_admin");
Collection<? extends GrantedAuthority> authorities
= AuthorityUtils.createAuthorityList(dbAuthSet.toArray(new String[0]));
//返回myUser的对象,密码需要passwordEncoder进行加密.以后后续校验的时候是加密之后的密码
return new MyUser(username,passwordEncoder.encode(password),userId,authorities);
}
}
/**
* 后续可以根据业务需求,添加相应的字段内容
*/
public class MyUser extends User {
private static final long serialVersionUID = 1L;
@Getter
private Integer userId;
public MyUser(String username, String password,Integer userId,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.userId = userId;
}
}
2. 配置WebSecurityConfigurerAdapter
WebSecurityConfigurerAdapter是spring security的适配器, 在配置的时候,需要我们自己写个配置类去继承他,然后编写自己所特殊需要的配置,代码如下:
/**
* 登录验证
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.failureForwardUrl("/login?error")
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index")
.permitAll()
.and()
.httpBasic()
.disable();
}
@Override
@SneakyThrows
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/css/**", "/js/**","/websocket/**", "/index.html",
"/img/**", "/fonts/**", "/favicon.ico");
}
}
完成上面三个配置,哪么就已经配置好spring security了。不过完成这些并不够,因为spring security 只是将会话信息放到session中,如果会话关闭的话,哪么用户就需要重新登录,这肯定不满足我们的需求。因此我们就需要在进一步扩展.
|