diff --git a/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileAuthenticationFilter.java b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileAuthenticationFilter.java new file mode 100644 index 00000000..0608fe14 --- /dev/null +++ b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileAuthenticationFilter.java @@ -0,0 +1,76 @@ +package com.github.pig.auth.component.mobile; + +import com.github.pig.common.constant.SecurityConstants; +import org.springframework.http.HttpMethod; +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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @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; + } +} + diff --git a/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileAuthenticationProvider.java b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileAuthenticationProvider.java new file mode 100644 index 00000000..3f52d8d1 --- /dev/null +++ b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileAuthenticationProvider.java @@ -0,0 +1,50 @@ +package com.github.pig.auth.component.mobile; + +import com.github.pig.auth.feign.UserService; +import com.github.pig.auth.util.UserDetailsImpl; +import com.github.pig.common.vo.UserVo; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * @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; + } +} diff --git a/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileAuthenticationToken.java b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileAuthenticationToken.java new file mode 100644 index 00000000..860cc068 --- /dev/null +++ b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileAuthenticationToken.java @@ -0,0 +1,56 @@ +package com.github.pig.auth.component.mobile; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.SpringSecurityCoreVersion; + +import java.util.Collection; + +/** + * @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 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(); + } +} + diff --git a/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileLoginSuccessHandler.java b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileLoginSuccessHandler.java new file mode 100644 index 00000000..125bb679 --- /dev/null +++ b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileLoginSuccessHandler.java @@ -0,0 +1,113 @@ +package com.github.pig.auth.component.mobile; + +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.pig.common.constant.CommonConstant; +import com.xiaoleilu.hutool.util.MapUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * @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; + + /** + * Called when a user has been successfully authenticated. + * 调用spring security oauth API 生成 oAuth2AccessToken + * + * @param request the request which caused the successful authentication + * @param response the response + * @param authentication the Authentication object which was created during + */ + @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)}; + } + + +} diff --git a/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileSecurityConfigurer.java b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileSecurityConfigurer.java new file mode 100644 index 00000000..2a60dfe6 --- /dev/null +++ b/pig-auth-service/src/main/java/com/github/pig/auth/component/mobile/MobileSecurityConfigurer.java @@ -0,0 +1,35 @@ +package com.github.pig.auth.component.mobile; + +import com.github.pig.auth.feign.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.stereotype.Component; + +/** + * @author lengleng + * @date 2018/1/9 + * 手机号登录配置入口 + */ +@Component +public class MobileSecurityConfigurer extends SecurityConfigurerAdapter { + @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); + } +}