实现多因素认证的客户端 整体思路
- 自定义过滤器继承OncePerRequestFilter,判断code值是否存在
- code值不存在,走自定义的用户名密码认证逻辑UsernamePasswordAuthenticationProvider,并返回authcode
- code值存在,走自定义的用户名认证码认证逻辑,并返回token
- 集成访问认证服务的方法
代码详细介绍
- 基于上一集的认证服务,我们来实现一个客户端,新建一个springboot项目,名字为 business-service, 引入security依赖,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- application.yml 做如下配置,修改端口号,不和认证服务冲突,加入auth配置指向认证服务
spring:
application:
name: business-service
server:
port: 8081
auth:
server:
base:
url: http://localhost:8080
- 新建User类
package com.kai.oauth.securityservice.authentication.model;
import lombok.Data;
@Data
public class User {
private String username;
private String password;
private String code;
}
- 新建两个自定义认证令牌类 AuthCodeAuthentication 和 UsernamePasswordAuthentication,他们都继承 UsernamePasswordAuthenticationToken类,
package com.kai.oauth.securityservice.authentication;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public class AuthCodeAuthentication extends UsernamePasswordAuthenticationToken {
public AuthCodeAuthentication(Object principal, Object credentials) {
//重用了其存储 principal(用户名)和 credentials(验证码)的功能
super(principal, credentials);
}
}
package com.kai.oauth.securityservice.authentication;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
public class UsernamePasswordAuthentication extends UsernamePasswordAuthenticationToken {
//存储authcode属性
private String authcode;
public UsernamePasswordAuthentication(Object principal, Object credentials) {
super(principal, credentials);
}
public UsernamePasswordAuthentication(Object principal, Object credentials,String authcode) {
super(principal, credentials);
this.authcode = authcode;
}
public UsernamePasswordAuthentication(Object principal, String authcode,Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
this.authcode = authcode;
}
//获取authcode方法
public String getAuthcode() {
return this.authcode;
}
}
- 自定义两个认证提供器,AuthCodeAuthenticationProvider(认证码认证),UsernamePasswordAuthenticationProvider(密码认证),它们都实现了AuthenticationProvider接口
package com.kai.oauth.securityservice.authentication.provider;
import com.kai.oauth.securityservice.authentication.AuthCodeAuthentication;
import com.kai.oauth.securityservice.authentication.facade.AuthenticationServerFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
@Component
public class AuthCodeAuthenticationProvider implements AuthenticationProvider {
@Autowired
private AuthenticationServerFacade authServer;
//执行实际的认证逻辑
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//获取用户名
String username = authentication.getName();
//获取认证码
String code = String.valueOf(authentication.getCredentials());
//检验 用户名和验证码
boolean result = authServer.checkAuthCode(username, code);
if(result){
//验证通过,创建AuthCodeAuthentication对象,并传入用户名和认证码
return new AuthCodeAuthentication(username,code);
}else{
throw new BadCredentialsException("Bad credentials");
}
}
//表示该 Provider 只支持 AuthCodeAuthentication 类型的认证请求
@Override
public boolean supports(Class<?> aClass) {
return AuthCodeAuthentication.class.isAssignableFrom(aClass);
}
}
package com.kai.oauth.securityservice.authentication.provider;
import com.kai.oauth.securityservice.authentication.UsernamePasswordAuthentication;
import com.kai.oauth.securityservice.authentication.facade.AuthenticationServerFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
@Component
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
@Autowired
private AuthenticationServerFacade authServer;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = String.valueOf(authentication.getCredentials());
//获取认证码
String authcode = authServer.checkPassword(username,password);
return new UsernamePasswordAuthentication(username,password,authcode);
}
@Override
public boolean supports(Class<?> aClass) {
return UsernamePasswordAuthentication.class.isAssignableFrom(aClass);
}
}
- 新建AuthenticationServerFacade类,基于门面模式 来调用认证服务 暴露的两个端点
package com.kai.oauth.securityservice.authentication.facade;
import com.kai.oauth.securityservice.authentication.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Component
public class AuthenticationServerFacade {
@Autowired
private RestTemplate restTemplate;
@Value("${auth.server.base.url}")
private String baseUrl;
public String checkPassword(String username,String password){
String url = baseUrl + "/user/auth";
User user = new User();
user.setUsername(username);
user.setPassword(password);
HttpEntity<User> request = new HttpEntity<>(user);
ResponseEntity<Map<String, String>> response = restTemplate.exchange(
url,
HttpMethod.POST,
request,
new ParameterizedTypeReference<Map<String, String>>() {}
);
return response.getBody().get("authcode");
}
public boolean checkAuthCode(String username,String code){
String url = baseUrl + "/authcode/check";
User user = new User();
user.setUsername(username);
user.setCode(code);
HttpEntity<User> request = new HttpEntity<User>(user);
ResponseEntity<Void> response = restTemplate.postForEntity(url,request, Void.class);
return response.getStatusCode().equals(HttpStatus.OK);
}
}
- 创建一个自定义过滤器CustomAuthenticationFilter 继承 OncePerRequestFilter ,表示每次请求都执行一次
package com.kai.oauth.securityservice.authentication.filter;
import com.kai.oauth.securityservice.authentication.AuthCodeAuthentication;
import com.kai.oauth.securityservice.authentication.UsernamePasswordAuthentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
@Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private AuthenticationManager manager;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String username = request.getHeader("username");
String password = request.getHeader("password");
String code = request.getHeader("code");
if(code == null){
Authentication a = new UsernamePasswordAuthentication(username, password);
Authentication authenticated = manager.authenticate(a);
if (authenticated instanceof UsernamePasswordAuthentication) {
String authcode = ((UsernamePasswordAuthentication) authenticated).getAuthcode();
response.setHeader("Authcode", authcode);
}
}else{
Authentication a = new AuthCodeAuthentication(username, code);
manager.authenticate(a);
String token = UUID.randomUUID().toString().replaceAll("-", "");
response.setHeader("Authorization",token);
}
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !request.getServletPath().equals("/login");
}
}
- 新建SecurityConfig来配置 security基础功能
package com.kai.oauth.securityservice.config;
import com.kai.oauth.securityservice.authentication.filter.CustomAuthenticationFilter;
import com.kai.oauth.securityservice.authentication.provider.AuthCodeAuthenticationProvider;
import com.kai.oauth.securityservice.authentication.provider.UsernamePasswordAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthCodeAuthenticationProvider authCodeAuthenticationProvider;
@Autowired
private UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider;
@Autowired
private CustomAuthenticationFilter customAuthenticationFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//把自定义的两个认证逻辑添加到security中
auth.authenticationProvider(authCodeAuthenticationProvider)
.authenticationProvider(usernamePasswordAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用csrf
http.csrf().disable();
//把自定义过滤器添加到security中
http.addFilterAfter(customAuthenticationFilter, BasicAuthenticationFilter.class);
//任何请求都需要认证
http.authorizeRequests().anyRequest().authenticated();
}
//创建AuthenticationManager对象
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
- 最后别忘了在启动文件中添加 restTemplate的bean
package com.kai.oauth.securityservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class BusinessServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BusinessServiceApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
10 用postman测试 发送用户名和密码时 返回的header中已经包含了Authcode

用生产的authcode请求时 返回了 随机生成的token

由于token只是随机的UUID,所以这里只演示到 获取token。 后续我们引入JWT在继续演示获取token之后如何发送token来实现用户认证

293

被折叠的 条评论
为什么被折叠?



