SpringSecurity 如何进行邮箱登录(手机登录)

SpringSecurity 授权流程

SpringSecurity 授权流程
简而言之,
      默认的 用户名密码登录 就是 登录的时候 被对应的UsernamePasswordAuthenticationFilter拦截下来,然后通过前端传过来的Username,Password组成一个叫UsernamePasswordAuthenticationToken 的东西,这玩意基本可以看做是封装了用户名、密码、权限、SessionId 等信息的token,当然,在未验证的时候,他只有接受到用户名和密码。
      紧接着 他会把这个token 传递给你写的 provider 进行验证,用户名密码登录的话会用实现的UserDetailService进行判断,验证通过则把 详细的user 信息 以及 权限信息放入 之前的 token 中,并且把Authentication信息设置成true,即标识为已被认证。



但是 , 在手机登录以及邮箱登录的时候,并没有用到用户名,密码等,之前默认的userDetailService 实现的是根据用户名查询,并不是手机和邮箱,也没有验证验证码的功能, 那么显然我们需要重新写 以下几个模块
1.EmailCodeAuthenticationToken
用于存放前端传来的信息
2.EmailCodeAuthenticationFilter
对特定手机登录URL进行过滤,封装token交由provider进行认证
3.EmailCodeAuthenticaitonProvider
认证token
4.successHandler、failHandler
分别对应 认证成功后跳转的位置和失败后跳转的位置
5.EmailCodeAuthenticationSecurityConfig
配置过滤器 以及 handler,provider
6 配置SpringSecurityconfig

1EmailCodeAuthenticationToken

package com.how2j.copy.security.token;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import javax.security.auth.Subject;
import java.util.Collection;

public class EmailCodeAuthenticationToken extends AbstractAuthenticationToken {


    //这里的 principal 指的是 email 地址(未认证的时候)
     private final Object principal;

    public EmailCodeAuthenticationToken(Object principal) {
        super((Collection)null);
        this.principal = principal;
        setAuthenticated(false);
    }

    public EmailCodeAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object principal) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true);
    }

    //如果是Set认证状态,就无情的给一个异常,意思是:
    //不要在这里设置已认证,不要在这里设置已认证,不要在这里设置已认证
    //应该从构造方法里创建,别忘了要带上用户信息和权限列表哦
    //原来如此,是避免犯错吧
    /*
上面是复制的 接下来个人理解->
第一次 使用token 是未认证的 principal 里存放的是用户名
第二次 是认证的 该token 里面 还存放了 该用户的权限
springsecurity 通过这个标志来判断 是否被认证

     */
    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");
        } else {
            super.setAuthenticated(false);
        }
    }



    /********************父类抽象方法***************************/


    @Override
    public boolean implies(Subject subject) {
        return false;
    }

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

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

2EmailCodeAuthenticationFilter

package com.how2j.copy.security.filter;

import com.how2j.copy.security.token.EmailCodeAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

public class EmailCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter  {
    /**
     * 前端传来的 参数名 - 用于request.getParameter 获取
     */
    private final String DEFAULT_EMAIL_NAME="email";
    private final String DEFAULT_EMAIL_CODE="e_code";
    //是否 仅仅post
    private boolean postOnly = true;

    /**
     * 父类中是 调用setFilterProcessesUrl(defaultFilterProcessesUrl); 方法创建可以通过的url
     * 精确绑定url
     * @param defaultFilterProcessesUrl
     */
    public EmailCodeAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }

    /**
     * 通过 传入的 参数 创建 匹配器
     * 即 Filter过滤的url
     * @param requiresAuthenticationRequestMatcher
     */
    public EmailCodeAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
        super(new AntPathRequestMatcher("/account/login/email","POST"));
    }

    /**
     * 给权限
     * filter 获得 用户名(邮箱) 和 密码(验证码) 装配到 token 上 ,
     * 然后把token 交给 provider 进行授权
     * @param httpServletResponse
     * @return
     * @throws AuthenticationException
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
        HttpSession session = request.getSession();
        if(postOnly && !request.getMethod().equals("POST") ){
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }else{

            String email = getEmail(request);
            if(email == null){
                email = "";
            }
            email = email.trim();
            //如果 验证码不相等 故意让token出错 然后走springsecurity 错误的流程
            boolean flag = isCodeEquals(request);
            /*
            封装 token
             */
            EmailCodeAuthenticationToken token = null;
            if(flag){
                token  = new EmailCodeAuthenticationToken(email);
            }else{
                token = new EmailCodeAuthenticationToken("error");
            }
            this.setDetails(request,token);
            /*
            交给 manager 发证
             */
            return this.getAuthenticationManager().authenticate(token);
        }

    //    return null;
    }

    /**
     * 获取 头部信息 让合适的provider 来验证他
     * @param request
     * @param token
     */
    public void setDetails(HttpServletRequest request , EmailCodeAuthenticationToken token ){
    token.setDetails(this.authenticationDetailsSource.buildDetails(request ));
    }

    /**
     * 获取 传来 的Email信息
     */
    public String getEmail(HttpServletRequest request ){
        String result=  request.getParameter(DEFAULT_EMAIL_NAME);
        return result;
    }
    /**
     * 判断 传来的 验证码信息 以及 session 中的验证码信息
     */
    public boolean isCodeEquals(HttpServletRequest request ){
        HttpSession session = request.getSession();
        String code1 = request.getParameter(DEFAULT_EMAIL_CODE);
        System.out.println("code1**********"+code1);
        String code2 =  (String)session.getAttribute(DEFAULT_EMAIL_CODE+getEmail(request));
        System.out.println("code2***********"+code2 );
        try{
            return code1.equals(code2);
        }catch (Exception e){
            return false;
        }



    }

}

3 EmailCodeAuthentictionProvider

package com.how2j.copy.security.provider;

import com.how2j.copy.security.token.EmailCodeAuthenticationToken;
import com.how2j.copy.service.UserService;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;

public class EmailCodeAuthentictionProvider implements AuthenticationProvider {

    UserService userService ;

    public EmailCodeAuthentictionProvider(UserService userService) {
        this.userService = userService;
    }

    /**
     * 认证
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        EmailCodeAuthenticationToken token = (EmailCodeAuthenticationToken)authentication;
        UserDetails user = userService.getByEmail((String)token.getPrincipal());
        System.out.println(token.getPrincipal());
        if(user == null){
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }
        System.out.println(user.getAuthorities());
        UsernamePasswordAuthenticationToken result =
                new UsernamePasswordAuthenticationToken(user,user.getPassword(),user.getAuthorities());
                /*
                Details 中包含了 ip地址、 sessionId 等等属性
                */
        result.setDetails(token.getDetails());
        return result;
    }

    @Override
    public boolean supports(Class<?> aClass) {

        return EmailCodeAuthenticationToken.class.isAssignableFrom(aClass);
    }
}

4、handler

1

package com.how2j.copy.security.handler;

import org.springframework.context.annotation.Scope;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/*
我这里加上scope 的原因是
spring中 autowired 装配的对象是单例的;
那么问题来了,
因为我 既有 邮箱登录 又有 密码登录,这两个我使用同一个handler ,
但是它们失败后跳转的url 又是不一样的,所以通过一个 tar 属性动态的绑定不同的handle
如果是单例的 , 那么后一个配置的tar 会 覆盖 前一个配置的tar ,
从而导致 邮件也好 , 手机也好 都跳转到 同一个位置,这是我们不想要的,
所以使用 多例 的@Scope 注解来保证绑定的是不同的handle
 */
@Component
@Scope("prototype")
public class MyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
/*
要跳转的位置
 */
    String tar  ;

    public String getTar() {
        return tar;
    }

    public void setTar(String tar) {
        this.tar = tar;
    }

    /**
     * 跳转
     * @param request
     * @param response
     * @param exception
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        super.setDefaultFailureUrl("/account/login/"+tar+"?msg=error");
        super.onAuthenticationFailure(request, response, exception);
    }
}

2

package com.how2j.copy.security.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {

        super.setDefaultTargetUrl("/");
        super.onAuthenticationSuccess(request, response, authentication);
    }
}

5、EmailCodeAuthenticationSecurityConfig

package com.how2j.copy.security.config;

import com.how2j.copy.security.filter.EmailCodeAuthenticationFilter;
import com.how2j.copy.security.handler.MyAuthenticationFailHandler;
import com.how2j.copy.security.handler.MyAuthenticationSuccessHandler;
import com.how2j.copy.security.provider.EmailCodeAuthentictionProvider;
import com.how2j.copy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

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

    @Autowired
    private MyAuthenticationFailHandler myAuthenticationFailHandler;

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private UserService userService ;


    @Override
    public void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        myAuthenticationFailHandler.setTar("email");
        EmailCodeAuthenticationFilter filter = new EmailCodeAuthenticationFilter(new AntPathRequestMatcher("/account/login/email","POST")) ;
        filter .setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        filter .setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
        filter .setAuthenticationFailureHandler(myAuthenticationFailHandler);

        EmailCodeAuthentictionProvider provider = new EmailCodeAuthentictionProvider(userService) ;
        http.authenticationProvider(provider)
                .addFilterAfter(filter , UsernamePasswordAuthenticationFilter.class);
    }
}

6、configure

   protected void configure(HttpSecurity http) throws Exception {
        // TODO Auto-generated method stub

        http
                .authorizeRequests()
                .antMatchers("/css/**","/js/**","/fonts/**","/index")
                .permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .and()
                .formLogin()
                .loginPage("/account/login").failureUrl("/account/login?msg=error").defaultSuccessUrl("/")
                .and()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/")
                .and()
                .rememberMe()
                .and()
                .exceptionHandling().accessDeniedPage("/403");
        http.csrf().disable();
        http.headers().frameOptions().sameOrigin();
        http.apply(emailCodeAuthenticationSecurityConfig );
        http.apply(smsCodeAuthenticationSecurityConfig );
    }

只要加最后的apply即可

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值