学成-认证实现

一、Spring-security

1.集成Spring-security

依赖引入

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

配置类导入

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
36
37
38
39
40
41
42

/**
* @author Mr.M
* @version 1.0
* @description 安全管理配置
* @date 2022/9/26 20:53
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


//配置用户信息服务
@Bean
public UserDetailsService userDetailsService() {
//这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;
}

@Bean
public PasswordEncoder passwordEncoder() {
// //密码为明文方式
return NoOpPasswordEncoder.getInstance();
// return new BCryptPasswordEncoder();
}

//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过
.anyRequest().permitAll()//其它请求全部放行
.and()
.formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success
}

}

接口授权

1
2
3
4
5
6
7
8
@Bean
public UserDetailsService userDetailsService() {
//这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;
}

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class LoginController {
....
@RequestMapping("/r/r1")
@PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问
public String r1(){
return "访问r1资源";
}

@RequestMapping("/r/r2")
@PreAuthorize("hasAuthority('p2')")//拥有p2权限方可访问
public String r2(){
return "访问r2资源";
}
...

访问/r/r1,使用zhangsan登录可以正常访问,因为在/r/r1的方法上指定了权限p1,zhangsan用户拥有权限p1,所以可以正常访问

访问/r/r1,使用lisi登录则拒绝访问,由于lisi用户不具有权限p1需要拒绝访问

2.OAuth2授权

image-20260319090846996

OAuth2(开放授权2.0)是目前最广泛使用的授权框架,用于让第三方应用安全地访问用户在某个服务上的资源,而不需要暴露用户的密码

理解 OAuth2 的核心:它解决的问题是”我信任 Google,但我不想把 Google 密码告诉某个第三方 App”

3.网关实现鉴权

1
2
3
路由转发
JWT校验
白名单维护

二、用户认证

1.连接数据库实现认证

屏蔽原来定义的UserDetailsService

自定义UserDetailsService

2.修改密码为硬编码

1
2
3
4
5
6
    @Bean
public PasswordEncoder passwordEncoder() {
// //密码为明文方式
// return NoOpPasswordEncoder.getInstance();
return new BCryptPasswordEncoder();
}

扩展用户身份信息

1
2
3
4
5
6
7
8
9
10
 //取出数据库存储的正确密码
String password =user.getPassword();
//用户权限,如果不加报Cannot pass a null GrantedAuthority collection
String[] authorities = {"p1"};
//为了安全在令牌中不放密码
user.setPassword(null);
//将user对象转json
String userString = JSON.toJSONString(user);
//创建UserDetails对象
UserDetails userDetails = User.withUsername(userString).password(password).authorities(authorities).build();

将用户信息转成json字串直接存入

3.获取用户身份

1
2
3
4
5
6
7
Object principalObj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principalObj instanceof String) {
//取出用户身份信息
String principal = principalObj.toString();
//将json转成对象
XcUser user = JSON.parseObject(principal, XcUser.class);
return user;

本质上就是基于上下文取出的

4.统一认证入口

默认的DaoAuthenticationProvider 会进行密码校验,现在重新定义DaoAuthenticationProviderCustom类,重写类的additionalAuthenticationChecks方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@Component
public class DaoAuthenticationProviderCustom extends DaoAuthenticationProvider {

@Autowired
public void setUserDetailsService(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}

//屏蔽密码对比
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {

}
}

接着注入自定义DaoAuthenticationProvider

1
2
3
4
5
6
7
8
@Autowired
DaoAuthenticationProviderCustom daoAuthenticationProviderCustom;


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProviderCustom);
}

根据不同类型,取出不同的 bean 执行

1
2
3
4
//认证方法
String authType = authParamsDto.getAuthType();
AuthService authService = applicationContext.getBean(authType + "_authservice", AuthService.class);
XcUserExt user = authService.execute(authParamsDto);

5.验证码功能

image-20260319133323028

核心思路:验证码的本质是”临时共享秘密“,前端拿着 key,后端用 key 去 Redis 找答案,比对一致则通过,用完即删6

6.微信验证码

image-20260319202805704

所以我们这里不用所谓的内网穿透也可以成功实现

302重定向 就是微信服务器告诉浏览器:

“你自己去这个地址,code我已经拼在URL里了”

code 从来没有经过微信服务器→你内网这条路,而是:

  • 微信服务器 → 浏览器(302响应,code在Location header里)
  • 浏览器 → localhost(浏览器自己发的请求,code在URL参数里)