security第十五集 多因素认证-客户端

实现多因素认证的客户端 整体思路

  • 自定义过滤器继承OncePerRequestFilter,判断code值是否存在
  • code值不存在,走自定义的用户名密码认证逻辑UsernamePasswordAuthenticationProvider,并返回authcode
  • code值存在,走自定义的用户名认证码认证逻辑,并返回token
  • 集成访问认证服务的方法

代码详细介绍

  1. 基于上一集的认证服务,我们来实现一个客户端,新建一个springboot项目,名字为 business-service, 引入security依赖,
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
  1. application.yml 做如下配置,修改端口号,不和认证服务冲突,加入auth配置指向认证服务
spring:
    application:
        name: business-service
server:
    port: 8081
auth:
    server:
        base:
            url: http://localhost:8080
  1. 新建User类
package com.kai.oauth.securityservice.authentication.model;

import lombok.Data;
@Data
public class User {
    private String username;
    private String password;
    private String code;
}
  1. 新建两个自定义认证令牌类 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;
    }
}

  1. 自定义两个认证提供器,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);
    }
}

  1. 新建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);
    }
}

  1. 创建一个自定义过滤器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");
    }
}

  1. 新建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();
    }
}
  1. 最后别忘了在启动文件中添加 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来实现用户认证

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凯凯凯凯神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值