Spring Security Oauth2.0 实现短信验证码登录

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

spring security oauth2 登录过程详解

201910202000\_1.png

定义手机号登录令牌

    /**
     * @author lengleng
     * @date 2018/1/9
     * 手机号登录令牌
     */
    public class MobileAuthenticationToken extends AbstractAuthenticationToken {

        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

        private final Object principal;

        public MobileAuthenticationToken(String mobile) {
            super(null);
            this.principal = mobile;
            setAuthenticated(false);
        }

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

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

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

        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();
        }
    }

手机号登录校验逻辑

    /**
     * @author lengleng
     * @date 2018/1/9
     * 手机号登录校验逻辑
     */
    public class MobileAuthenticationProvider implements AuthenticationProvider {
        private UserService userService;

        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            MobileAuthenticationToken mobileAuthenticationToken = (MobileAuthenticationToken) authentication;
            UserVo userVo = userService.findUserByMobile((String) mobileAuthenticationToken.getPrincipal());

            UserDetailsImpl userDetails = buildUserDeatils(userVo);
            if (userDetails == null) {
                throw new InternalAuthenticationServiceException("手机号不存在:" + mobileAuthenticationToken.getPrincipal());
            }

            MobileAuthenticationToken authenticationToken = new MobileAuthenticationToken(userDetails, userDetails.getAuthorities());
            authenticationToken.setDetails(mobileAuthenticationToken.getDetails());
            return authenticationToken;
        }

        private UserDetailsImpl buildUserDeatils(UserVo userVo) {
            return new UserDetailsImpl(userVo);
        }

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

        public UserService getUserService() {
            return userService;
        }

        public void setUserService(UserService userService) {
            this.userService = userService;
        }
    }

登录过程filter处理

    **
     * @author lengleng
     * @date 2018/1/9
     * 手机号登录验证filter
     */
    public class MobileAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
        public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";

        private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
        private boolean postOnly = true;

        public MobileAuthenticationFilter() {
            super(new AntPathRequestMatcher(SecurityConstants.MOBILE_TOKEN_URL, "POST"));
        }

        public Authentication attemptAuthentication(HttpServletRequest request,
                                                    HttpServletResponse response) throws AuthenticationException {
            if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
                throw new AuthenticationServiceException(
                        "Authentication method not supported: " + request.getMethod());
            }

            String mobile = obtainMobile(request);

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

            mobile = mobile.trim();

            MobileAuthenticationToken mobileAuthenticationToken = new MobileAuthenticationToken(mobile);

            setDetails(request, mobileAuthenticationToken);

            return this.getAuthenticationManager().authenticate(mobileAuthenticationToken);
        }

        protected String obtainMobile(HttpServletRequest request) {
            return request.getParameter(mobileParameter);
        }

        protected void setDetails(HttpServletRequest request,
                                  MobileAuthenticationToken authRequest) {
            authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        }

        public void setPostOnly(boolean postOnly) {
            this.postOnly = postOnly;
        }

        public String getMobileParameter() {
            return mobileParameter;
        }

        public void setMobileParameter(String mobileParameter) {
            this.mobileParameter = mobileParameter;
        }

        public boolean isPostOnly() {
            return postOnly;
        }
    }

生产token 位置

    /**
     * @author lengleng
     * @date 2018/1/8
     * 手机号登录成功,返回oauth token
     */
    @Component
    public class MobileLoginSuccessHandler implements org.springframework.security.web.authentication.AuthenticationSuccessHandler {
        private Logger logger = LoggerFactory.getLogger(getClass());
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private ClientDetailsService clientDetailsService;
        @Autowired
        private AuthorizationServerTokenServices authorizationServerTokenServices;

        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
            String header = request.getHeader("Authorization");

            if (header == null || !header.startsWith("Basic ")) {
                throw new UnapprovedClientAuthenticationException("请求头中client信息为空");
            }

            try {
                String[] tokens = extractAndDecodeHeader(header);
                assert tokens.length == 2;
                String clientId = tokens[0];
                String clientSecret = tokens[1];

                JSONObject params = new JSONObject();
                params.put("clientId", clientId);
                params.put("clientSecret", clientSecret);
                params.put("authentication", authentication);

                ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
                TokenRequest tokenRequest = new TokenRequest(MapUtil.newHashMap(), clientId, clientDetails.getScope(), "mobile");
                OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

                OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
                OAuth2AccessToken oAuth2AccessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
                logger.info("获取token 成功:{}", oAuth2AccessToken.getValue());

                response.setCharacterEncoding(CommonConstant.UTF8);
                response.setContentType(CommonConstant.CONTENT_TYPE);
                PrintWriter printWriter = response.getWriter();
                printWriter.append(objectMapper.writeValueAsString(oAuth2AccessToken));
            } catch (IOException e) {
                throw new BadCredentialsException(
                        "Failed to decode basic authentication token");
            }
        }

        /**
         * Decodes the header into a username and password.
         *
         * @throws BadCredentialsException if the Basic header is not present or is not valid
         *                                 Base64
         */
        private String[] extractAndDecodeHeader(String header)
                throws IOException {

            byte[] base64Token = header.substring(6).getBytes("UTF-8");
            byte[] decoded;
            try {
                decoded = Base64.decode(base64Token);
            } catch (IllegalArgumentException e) {
                throw new BadCredentialsException(
                        "Failed to decode basic authentication token");
            }

            String token = new String(decoded, CommonConstant.UTF8);

            int delim = token.indexOf(":");

            if (delim == -1) {
                throw new BadCredentialsException("Invalid basic authentication token");
            }
            return new String[]{token.substring(0, delim), token.substring(delim + 1)};
        }
    }

配置以上自定义

    //**
     * @author lengleng
     * @date 2018/1/9
     * 手机号登录配置入口
     */
    @Component
    public class MobileSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
        @Autowired
        private MobileLoginSuccessHandler mobileLoginSuccessHandler;
        @Autowired
        private UserService userService;

        @Override
        public void configure(HttpSecurity http) throws Exception {
            MobileAuthenticationFilter mobileAuthenticationFilter = new MobileAuthenticationFilter();
            mobileAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
            mobileAuthenticationFilter.setAuthenticationSuccessHandler(mobileLoginSuccessHandler);

            MobileAuthenticationProvider mobileAuthenticationProvider = new MobileAuthenticationProvider();
            mobileAuthenticationProvider.setUserService(userService);
            http.authenticationProvider(mobileAuthenticationProvider)
                    .addFilterAfter(mobileAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }

在spring security 配置 上边定一个的那个聚合配置

    /**
     * @author lengleng
     * @date 2018年01月09日14:01:25
     * 认证服务器开放接口配置
     */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
        @Autowired
        private FilterUrlsPropertiesConifg filterUrlsPropertiesConifg;
        @Autowired
        private MobileSecurityConfigurer mobileSecurityConfigurer;

        @Override
        public void configure(HttpSecurity http) throws Exception {
            registry
                    .antMatchers("/mobile/token").permissionAll()
                    .anyRequest().authenticated()
                    .and()
                    .csrf().disable();
            http.apply(mobileSecurityConfigurer);
        }
    }

使用

    curl -H "Authorization:Basic cGlnOnBpZw==" -d "grant_type=mobile&scope=server&mobile=17034642119&code=" http://localhost:9999/auth/mobile/token

源码

  1. 请参考 https://gitee.com/log4j/
  2. 基于Spring Cloud、Spring Security Oauth2.0开发企业级认证与授权,提供常见服务监控、链路追踪、日志分析、缓存管理、任务调度等实现
  3. 整个逻辑是参考spring security 自身的 usernamepassword 登录模式实现,可以参考其源码。
  4. 验证码的发放、校验逻辑比较简单,方法后通过全局fiter 判断请求中code 是否和 手机号匹配集合,重点逻辑是令牌的参数
赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Security Oauth2.0 实现短信验证码登录

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏