봄 보안 구문 분석 (4 개) - 메시지 개발에 로그인

봄 보안 구문 분석 (4 개) - 메시지 개발에 로그인

  봄 클라우드를 학습 할 때, 이론 및 설계를 배우는 항상 빈약 한, 먼저 봄 보안, 봄 보안으로 OAuth2 및 인증 내용에 관련된 다른 권리로 결정했다 OAuth는 관련 콘텐츠 인증 서비스를 만나 다시 구성 할 수 있습니다. 기사의이 시리즈는 학습과 이해를 작성하는 과정에서, 침해, 알려주십시오 경우 인상을 강화하는 것입니다.

프로젝트 환경 :

  • JDK1.8
  • 봄 부팅 2.X
  • 봄 보안 5.x를

첫째, 어떻게 SMS 로그인 기능에 대한 보안의 기초 달성하기 위해?

  검토 구현 프로세스의 로그인 폼에서 보안 :

https://img2018.cnblogs.com/blog/1772687/201909/1772687-20190902225110478-797444218.jpg

  과정에서, 우리는 로그온 중에 구현 서브 클래스 다른 자매가 특별한 치료 또는이 있음을 발견
:

  • AuthenticationFilter : 로그인 요청을 차단하기위한;
  • 인증되지 않은 오브젝트 레퍼런스의 인증 방법으로서;
  • AuthenticationProvider에 인증 프로세스.

  그래서 우리는 완전히로 사용자 정의 할 수 있습니다 SmsAuthenticationFilter 가로 챌 수 SmsAuthenticationToken을 인증 데이터를 전송하는 SmsAuthenticationProvider의 인증 비즈니스 프로세스를. 우리가 달성하기 위해 AbstractAuthenticationProcessingFilter에 의해 doFilter의 UsernamePasswordAuthenticationFilter 알고 있지만 UsernamePasswordAuthenticationFilter 자체에만 attemptAuthentication () 메소드를 구현하기. 이 구성에 따르면, 우리의 SmsAuthenticationFilter 만 attemptAuthentication () 메소드를 달성하는 방법을 코드에게 그것을 확인하려면? 그런 다음 우리는 SmsAuthenticationFilter 전에 여과 필터를 달성하기 위해 검증 코드를 호출해야합니다 ValidateCodeFilter를 . 과정을 마친 후 다음 그림을 달성하기 위해 :

https://img2018.cnblogs.com/blog/1772687/201909/1772687-20190902225110744-1309305475.jpg

둘째, SMS 로그인 인증 개발자

(A) SmsAuthenticationFilter 달성

  아날로그 UsernamePasswordAuthenticationFilter의 SmsAuthenticationFilter 후 다음의 코드는 달성 :

@EqualsAndHashCode(callSuper = true)
@Data
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    // 获取request中传递手机号的参数名
    private String mobileParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE;

    private boolean postOnly = true;

    // 构造函数,主要配置其拦截器要拦截的请求地址url
    public SmsCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, "POST"));
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        // 判断请求是否为 POST 方式
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 调用 obtainMobile 方法从request中获取手机号
        String mobile = obtainMobile(request);

        if (mobile == null) {
            mobile = "";
        }

        mobile = mobile.trim();

        // 创建 未认证的  SmsCodeAuthenticationToken  对象
        SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);

        setDetails(request, authRequest);
        
        // 调用 认证方法
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * 获取手机号
     */
    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(mobileParameter);
    }
    
    /**
     * 原封不动照搬UsernamePasswordAuthenticationFilter 的实现 (注意这里是 SmsCodeAuthenticationToken  )
     */
    protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    /**
     * 开放设置 RemmemberMeServices 的set方法
     */
    @Override
    public void setRememberMeServices(RememberMeServices rememberMeServices) {
        super.setRememberMeServices(rememberMeServices);
    }
}

내부 구현은 몇 가지주의 사항이 있습니다 :

  • 매개 변수를 설정 속성 전송 전화 번호
  • 생성자 방법은 매개 변수는 주로 URL을 차단하는 데 사용할 제공되는 상위 클래스를 호출 한
  • 1, obtainMobile는, 전화 번호 정보 (2)를 얻을 개체 SmsCodeAuthenticationToken을 만듭니다 attemptAuthentication ()의 사본을 달성 UsernamePasswordAuthenticationFilter, 재활의 내부 필요가 2 점을
  • 메시지를 달성하기 위해 로그인도 내 여기에 기능 공개 setRememberMeServices () 메소드는 rememberMeServices을 설정하는 데 사용됩니다 기억해야합니다.

(B) SmsAuthenticationToken 달성

  우리는 같은 UsernamePasswordAuthenticationToken를가 SmsAuthenticationToken을 달성 시뮬레이션 :

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;


    private final Object principal;

    /**
     * 未认证时,内容为手机号
     * @param mobile
     */
    public SmsCodeAuthenticationToken(String mobile) {
        super(null);
        this.principal = mobile;
        setAuthenticated(false);
    }

    /**
     *
     * 认证成功后,其中为用户信息
     *
     * @param principal
     * @param authorities
     */
    public SmsCodeAuthenticationToken(Object principal,
                                      Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

  이 UsernamePasswordAuthenticationToken 대비, 우리는 다른 기본적으로 그대로, (암호로 이해 될 수있다) 자격 증명을 감소시켰다.

(C) SmsAuthenticationProvider 달성

  SmsCodeAuthenticationProvider 우리가 자신의 아이디어에 따라 쓸 수 있도록하여 DaoAuthenticationProvider를 참조하지 않고, 달성하기 위해 위탁 완전히 새로운 다른 인증이기 때문에. 우리가 실현 자신의 코드를 살펴 :

@Data
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

    private UserDetailsService userDetailsService;


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;

        UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());

        if (user == null) {
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }

        SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());

        authenticationResult.setDetails(authenticationToken.getDetails());

        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

  직접 후속의 AuthenticationProvider를 인증하고 () 및 지지부에 의해 구현되는 인터페이스 방법 (). 우리가 다른 제공자 참조 직접 물품 () 지지체이 현재 인증 SmsCodeAuthenticationToken 또는 서브 처리 여부 주로 결정된다. 우리가 직접 loadUserByUsername UserDetailsService의을 () 메소드는 그래서 여기에 우리가 전화 번호를 전달하고 사용자가 직접 현재의 사용자 인증의 성공을 맨 선고 될 정보를 찾아, 코드가 ValidateCodeFilter에 의해 확인 되었기 때문에 구현이 간단하고, 생성 () 호출 인증 인증 SmsCodeAuthenticationToken 반환.

(D) ValidateCodeFilter 달성

   ValidateCodeFilter 인증 코드는 우리의 이전에 설명한 것처럼, 우리는 인수 코드 레디 스를 통해 사용자 입력을 비교 확인 코드를 생성 설정 여기서

@Component
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {

    /**
     * 验证码校验失败处理器
     */
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;
    /**
     * 系统配置信息
     */
    @Autowired
    private SecurityProperties securityProperties;

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 存放所有需要校验验证码的url
     */
    private Map<String, String> urlMap = new HashMap<>();
    /**
     * 验证请求url与配置的url是否匹配的工具类
     */
    private AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * 初始化要拦截的url配置信息
     */
    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();

        urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, SecurityConstants.DEFAULT_PARAMETER_NAME_CODE_SMS);
        addUrlToMap(securityProperties.getSms().getSendSmsUrl(), SecurityConstants.DEFAULT_PARAMETER_NAME_CODE_SMS);
    }

    /**
     * 讲系统中配置的需要校验验证码的URL根据校验的类型放入map
     *
     * @param urlString
     * @param smsParam
     */
    protected void addUrlToMap(String urlString, String smsParam) {
        if (StringUtils.isNotBlank(urlString)) {
            String[] urls = StringUtils.splitByWholeSeparatorPreserveAllTokens(urlString, ",");
            for (String url : urls) {
                urlMap.put(url, smsParam);
            }
        }
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        String code = request.getParameter(getValidateCode(request));
        if (code != null) {
            try {
                String oldCode = stringRedisTemplate.opsForValue().get(request.getParameter(SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE));
                if (StringUtils.equalsIgnoreCase(oldCode,code)) {
                    logger.info("验证码校验通过");
                } else {
                    throw new ValidateCodeException("验证码失效或错误!");
                }
            } catch (AuthenticationException e) {
                authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }
        chain.doFilter(request, response);
    }

    /**
     * 获取校验码
     *
     * @param request
     * @return
     */
    private String getValidateCode(HttpServletRequest request) {
        String result = null;
        if (!StringUtils.equalsIgnoreCase(request.getMethod(), "get")) {
            Set<String> urls = urlMap.keySet();
            for (String url : urls) {
                if (pathMatcher.match(url, request.getRequestURI())) {
                    result = urlMap.get(url);
                }
            }
        }
        return result;
    }
}

다음은 주요보기 doFilterInternal 코드 유효성 검사 논리가 구현 될 수있다.

셋째, 어떻게 FilterChain 그것의 발효에 추가 한 SMS 필터를 설정하는 방법?

여기에 우리가 새 구성 클래스 SmsCodeAuthenticationSecurityConfig를 도입 할 필요가 다음과 같이 그 코드는 다음과 같습니다

@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler ;

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Resource
    private UserDetailsService userDetailsService;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
        // 设置 AuthenticationManager
        smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        // 分别设置成功和失败处理器
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
        // 设置 RememberMeServices
        smsCodeAuthenticationFilter.setRememberMeServices(http
                .getSharedObject(RememberMeServices.class));

        // 创建 SmsCodeAuthenticationProvider 并设置 userDetailsService
        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
        smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);

        // 将Provider添加到其中
        http.authenticationProvider(smsCodeAuthenticationProvider)
                // 将过滤器添加到UsernamePasswordAuthenticationFilter后面
                .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    }

마지막으로, 우리는 SpringSecurityConfig 구성 클래스 SmsCodeAuthenticationSecurityConfig를 참조해야합니다

http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class)
                .apply(smsCodeAuthenticationSecurityConfig)
                . ...

넷째, 새로운 인터페이스는 확인 코드 및 검증 코드 로그인 양식을 보내

   새로운 확인 코드 인터페이스 (주로 액세스 할 수 없음으로 설정) 보내기 :

    @GetMapping("/send/sms/{mobile}")
    public void sendSms(@PathVariable String mobile) {
        // 随机生成 6 位的数字串
        String code = RandomStringUtils.randomNumeric(6);
        // 通过 stringRedisTemplate 缓存到redis中 
        stringRedisTemplate.opsForValue().set(mobile, code, 60 * 5, TimeUnit.SECONDS);
        // 模拟发送短信验证码
        log.info("向手机: " + mobile + " 发送短信验证码是: " + code);
    }

   양식에 서명하는 코드를 추가합니다 :

// 注意这里的请求接口要与 SmsAuthenticationFilter的构造函数 设置的一致
<form action="/loginByMobile" method="post">
    <table>
        <tr>
            <td>手机号:</td>
            <td><input type="text" name="mobile" value="15680659123"></td>
        </tr>
        <tr>
            <td>短信验证码:</td>
            <td>
                <input type="text" name="smsCode">
                <a href="/send/sms/15680659123">发送验证码</a>
            </td>
        </tr>
        <tr>
            <td colspan='2'><input name="remember-me" type="checkbox" value="true"/>记住我</td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">登录</button>
            </td>
        </tr>
    </table>
</form>

다섯째, 개인 요약

  사실, 다른 로그인, 필터의 핵심, 인증 토큰, AuthenticationProvider에이 세 가지 포인트를 달성했다. 정리되어 있습니다 : 사용자 정의 SmsAuthenticationFilter을 하는 차단 인증 토큰을 인증 데이터를 전송하는 AuthenticationProvider에의 인증 비즈니스 프로세스를. 우리가 달성하기 위해 AbstractAuthenticationProcessingFilter에 의해 doFilter의 UsernamePasswordAuthenticationFilter 알고 있지만 UsernamePasswordAuthenticationFilter 자체에만 attemptAuthentication () 메소드를 구현하기. 이 구성에 따르면, 우리의 AuthenticationFilter은 () 메소드 attemptAuthentication을 달성, 그러나 동시에 AuthenticationFilter 전에 검증 필터를 달성하기 위해 필터를 호출해야합니다 ValidatFilter를 . 다음의 흐름도와 동일하게, 임의의 로그인이 방법으로 첨가 될 수있다 :

https://img2018.cnblogs.com/blog/1772687/201909/1772687-20190902225110744-1309305475.jpg

   이 문서에서는 코드 SMS 로그인 코드 저장소의 개발, 보안 모듈에 GitHub의 주소 프로젝트를 액세스 할 수 있습니다 설명 : https://github.com/BUG9/spring-security

         당신이에 관심이 있다면, 별을 환영 따라, 북마크, 지원을 전달!

추천

출처www.cnblogs.com/bug9/p/11449573.html