From 81f4ef6e1b26c9dabd2f9d8077e58d74f43754d4 Mon Sep 17 00:00:00 2001 From: mrbird Date: Wed, 26 Jun 2019 21:20:24 +0800 Subject: [PATCH] =?UTF-8?q?Spring=20Security=20OAuth2=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E4=BB=A4=E7=89=8C=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 65.Spring-Security-OAuth2-Config/pom.xml | 75 +++++++++++++++ .../mrbird/security/SecurityApplication.java | 13 +++ .../config/AuthorizationServerConfig.java | 74 ++++++++++++++ .../mrbird/security/config/JWTokenConfig.java | 33 +++++++ .../security/config/ResourceServerConfig.java | 46 +++++++++ .../security/config/SecurityConfig.java | 20 ++++ .../security/config/TokenStoreConfig.java | 24 +++++ .../security/controller/UserController.java | 26 +++++ .../controller/ValidateController.java | 33 +++++++ .../cc/mrbird/security/domain/MyUser.java | 67 +++++++++++++ .../security/enhancer/JWTokenEnhancer.java | 23 +++++ .../MyAuthenticationFailureHandler.java | 28 ++++++ .../MyAuthenticationSucessHandler.java | 96 +++++++++++++++++++ .../security/service/RedisCodeService.java | 60 ++++++++++++ .../security/service/UserDetailService.java | 43 +++++++++ .../smscode/SmsAuthenticationConfig.java | 40 ++++++++ .../smscode/SmsAuthenticationFilter.java | 69 +++++++++++++ .../smscode/SmsAuthenticationProvider.java | 41 ++++++++ .../smscode/SmsAuthenticationToken.java | 49 ++++++++++ .../security/validate/smscode/SmsCode.java | 18 ++++ .../validate/smscode/SmsCodeFilter.java | 60 ++++++++++++ .../src/main/resources/application.yml | 6 ++ 22 files changed, 944 insertions(+) create mode 100644 65.Spring-Security-OAuth2-Config/pom.xml create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/SecurityApplication.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/AuthorizationServerConfig.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/JWTokenConfig.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/ResourceServerConfig.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/SecurityConfig.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/TokenStoreConfig.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/controller/UserController.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/controller/ValidateController.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/domain/MyUser.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/enhancer/JWTokenEnhancer.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/handler/MyAuthenticationFailureHandler.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/handler/MyAuthenticationSucessHandler.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/service/RedisCodeService.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/service/UserDetailService.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationConfig.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationFilter.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationProvider.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationToken.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCode.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCodeFilter.java create mode 100644 65.Spring-Security-OAuth2-Config/src/main/resources/application.yml diff --git a/65.Spring-Security-OAuth2-Config/pom.xml b/65.Spring-Security-OAuth2-Config/pom.xml new file mode 100644 index 0000000..6f84662 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.6.RELEASE + + + cc.mrbird + security + 0.0.1-SNAPSHOT + security + Demo project for Spring Boot + + + 1.8 + Greenwich.SR1 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter + + + org.springframework.cloud + spring-cloud-starter-oauth2 + + + org.springframework.cloud + spring-cloud-starter-security + + + org.apache.commons + commons-lang3 + + + org.springframework.boot + spring-boot-starter-data-redis + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/SecurityApplication.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/SecurityApplication.java new file mode 100644 index 0000000..bae4d89 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/SecurityApplication.java @@ -0,0 +1,13 @@ +package cc.mrbird.security; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SecurityApplication { + + public static void main(String[] args) { + SpringApplication.run(SecurityApplication.class, args); + } + +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/AuthorizationServerConfig.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/AuthorizationServerConfig.java new file mode 100644 index 0000000..66b71d8 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/AuthorizationServerConfig.java @@ -0,0 +1,74 @@ +package cc.mrbird.security.config; + +import cc.mrbird.security.service.UserDetailService; +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.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author MrBird + */ +@Configuration +@EnableAuthorizationServer +public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + + // @Autowired + // private TokenStore redisTokenStore; + @Autowired + private TokenStore jwtTokenStore; + @Autowired + private JwtAccessTokenConverter jwtAccessTokenConverter; + @Autowired + private AuthenticationManager authenticationManager; + @Autowired + private TokenEnhancer tokenEnhancer; + @Autowired + private UserDetailService userDetailService; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) { + TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); + List enhancers = new ArrayList<>(); + enhancers.add(tokenEnhancer); + enhancers.add(jwtAccessTokenConverter); + enhancerChain.setTokenEnhancers(enhancers); + endpoints.authenticationManager(authenticationManager) + .tokenStore(jwtTokenStore) + .accessTokenConverter(jwtAccessTokenConverter) + .userDetailsService(userDetailService); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.inMemory() + .withClient("test1") + .secret(new BCryptPasswordEncoder().encode("test1111")) + .authorizedGrantTypes("password", "refresh_token") + .accessTokenValiditySeconds(3600) + .refreshTokenValiditySeconds(864000) + .scopes("all", "a", "b", "c") + .and() + .withClient("test2") + .secret(new BCryptPasswordEncoder().encode("test2222")) + .accessTokenValiditySeconds(7200); + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/JWTokenConfig.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/JWTokenConfig.java new file mode 100644 index 0000000..75dbf3e --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/JWTokenConfig.java @@ -0,0 +1,33 @@ +package cc.mrbird.security.config; + +import cc.mrbird.security.enhancer.JWTokenEnhancer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; + +/** + * @author MrBird + */ +@Configuration +public class JWTokenConfig { + + @Bean + public TokenStore jwtTokenStore() { + return new JwtTokenStore(jwtAccessTokenConverter()); + } + + @Bean + public JwtAccessTokenConverter jwtAccessTokenConverter() { + JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); + accessTokenConverter.setSigningKey("test_key"); // 签名密钥 + return accessTokenConverter; + } + + @Bean + public TokenEnhancer tokenEnhancer() { + return new JWTokenEnhancer(); + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/ResourceServerConfig.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/ResourceServerConfig.java new file mode 100644 index 0000000..64e3bac --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/ResourceServerConfig.java @@ -0,0 +1,46 @@ +package cc.mrbird.security.config; + +import cc.mrbird.security.handler.MyAuthenticationFailureHandler; +import cc.mrbird.security.handler.MyAuthenticationSucessHandler; +import cc.mrbird.security.validate.smscode.SmsAuthenticationConfig; +import cc.mrbird.security.validate.smscode.SmsCodeFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +/** + * @author MrBird + */ +@Configuration +@EnableResourceServer +public class ResourceServerConfig extends ResourceServerConfigurerAdapter { + + @Autowired + private MyAuthenticationSucessHandler authenticationSucessHandler; + @Autowired + private MyAuthenticationFailureHandler authenticationFailureHandler; + @Autowired + private SmsCodeFilter smsCodeFilter; + @Autowired + private SmsAuthenticationConfig smsAuthenticationConfig; + + @Override + public void configure(HttpSecurity http) throws Exception { + http.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加短信验证码校验过滤器 + .formLogin() // 表单登录 + .loginProcessingUrl("/login") // 处理表单登录 URL + .successHandler(authenticationSucessHandler) // 处理登录成功 + .failureHandler(authenticationFailureHandler) // 处理登录失败 + .and() + .authorizeRequests() // 授权配置 + .antMatchers("/code/sms").permitAll() + .anyRequest() // 所有请求 + .authenticated() // 都需要认证 + .and() + .csrf().disable() + .apply(smsAuthenticationConfig); + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/SecurityConfig.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/SecurityConfig.java new file mode 100644 index 0000000..0fc430b --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/SecurityConfig.java @@ -0,0 +1,20 @@ +package cc.mrbird.security.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +/** + * @author MrBird + */ +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Bean(name = BeanIds.AUTHENTICATION_MANAGER) + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/TokenStoreConfig.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/TokenStoreConfig.java new file mode 100644 index 0000000..ee7f288 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/config/TokenStoreConfig.java @@ -0,0 +1,24 @@ +package cc.mrbird.security.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; + +/** + * @author MrBird + */ +@Configuration +public class TokenStoreConfig { + + @Autowired + private RedisConnectionFactory redisConnectionFactory; + + // @Bean + // public TokenStore redisTokenStore(){ + // return new RedisTokenStore(redisConnectionFactory); + // } + +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/controller/UserController.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/controller/UserController.java new file mode 100644 index 0000000..9dcd5bc --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/controller/UserController.java @@ -0,0 +1,26 @@ +package cc.mrbird.security.controller; + +import io.jsonwebtoken.Jwts; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; + +/** + * @author MrBird + */ +@RestController +public class UserController { + + @GetMapping("index") + public Object index(@AuthenticationPrincipal Authentication authentication, HttpServletRequest request) { + String header = request.getHeader("Authorization"); + String token = StringUtils.substringAfter(header, "bearer "); + + return Jwts.parser().setSigningKey("test_key".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token).getBody(); + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/controller/ValidateController.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/controller/ValidateController.java new file mode 100644 index 0000000..73b98d3 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/controller/ValidateController.java @@ -0,0 +1,33 @@ +package cc.mrbird.security.controller; + +import cc.mrbird.security.service.RedisCodeService; +import cc.mrbird.security.validate.smscode.SmsCode; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.ServletWebRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@RestController +public class ValidateController { + + @Autowired + private RedisCodeService redisCodeService; + + @GetMapping("/code/sms") + public void createSmsCode(HttpServletRequest request, HttpServletResponse response, String mobile) throws Exception { + SmsCode smsCode = createSMSCode(); + redisCodeService.save(smsCode, new ServletWebRequest(request), mobile); + // 输出验证码到控制台代替短信发送服务 + System.out.println("手机号" + mobile + "的登录验证码为:" + smsCode.getCode() + ",有效时间为120秒"); + } + + private SmsCode createSMSCode() { + String code = RandomStringUtils.randomNumeric(6); + return new SmsCode(code); + } + +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/domain/MyUser.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/domain/MyUser.java new file mode 100644 index 0000000..6c44f0a --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/domain/MyUser.java @@ -0,0 +1,67 @@ +package cc.mrbird.security.domain; + +import java.io.Serializable; + +public class MyUser implements Serializable { + private static final long serialVersionUID = 3497935890426858541L; + + private String userName; + + private String password; + + private boolean accountNonExpired = true; + + private boolean accountNonLocked= true; + + private boolean credentialsNonExpired= true; + + private boolean enabled= true; + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isAccountNonExpired() { + return accountNonExpired; + } + + public void setAccountNonExpired(boolean accountNonExpired) { + this.accountNonExpired = accountNonExpired; + } + + public boolean isAccountNonLocked() { + return accountNonLocked; + } + + public void setAccountNonLocked(boolean accountNonLocked) { + this.accountNonLocked = accountNonLocked; + } + + public boolean isCredentialsNonExpired() { + return credentialsNonExpired; + } + + public void setCredentialsNonExpired(boolean credentialsNonExpired) { + this.credentialsNonExpired = credentialsNonExpired; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/enhancer/JWTokenEnhancer.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/enhancer/JWTokenEnhancer.java new file mode 100644 index 0000000..090be27 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/enhancer/JWTokenEnhancer.java @@ -0,0 +1,23 @@ +package cc.mrbird.security.enhancer; + +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author MrBird + */ +public class JWTokenEnhancer implements TokenEnhancer { + @Override + public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) { + Map info = new HashMap<>(); + info.put("message", "hello world"); + + ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info); + return oAuth2AccessToken; + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/handler/MyAuthenticationFailureHandler.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/handler/MyAuthenticationFailureHandler.java new file mode 100644 index 0000000..5ed93b4 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/handler/MyAuthenticationFailureHandler.java @@ -0,0 +1,28 @@ +package cc.mrbird.security.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { + + @Autowired + private ObjectMapper mapper; + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException { + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + response.setContentType("application/json;charset=utf-8"); + response.getWriter().write(mapper.writeValueAsString(exception.getMessage())); + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/handler/MyAuthenticationSucessHandler.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/handler/MyAuthenticationSucessHandler.java new file mode 100644 index 0000000..74aec8d --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/handler/MyAuthenticationSucessHandler.java @@ -0,0 +1,96 @@ +package cc.mrbird.security.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +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.password.PasswordEncoder; +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.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; + +@Component +public class MyAuthenticationSucessHandler implements AuthenticationSuccessHandler { + + private Logger log = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private ClientDetailsService clientDetailsService; + @Autowired + private AuthorizationServerTokenServices authorizationServerTokenServices; + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + // 1. 从请求头中获取 ClientId + String header = request.getHeader("Authorization"); + if (header == null || !header.startsWith("Basic ")) { + throw new UnapprovedClientAuthenticationException("请求头中无client信息"); + } + + String[] tokens = this.extractAndDecodeHeader(header, request); + String clientId = tokens[0]; + String clientSecret = tokens[1]; + + TokenRequest tokenRequest = null; + + // 2. 通过 ClientDetailsService 获取 ClientDetails + ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); + + // 3. 校验 ClientId和 ClientSecret的正确性 + if (clientDetails == null) { + throw new UnapprovedClientAuthenticationException("clientId:" + clientId + "对应的信息不存在"); + } else if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) { + throw new UnapprovedClientAuthenticationException("clientSecret不正确"); + } else { + // 4. 通过 TokenRequest构造器生成 TokenRequest + tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "custom"); + } + + // 5. 通过 TokenRequest的 createOAuth2Request方法获取 OAuth2Request + OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails); + // 6. 通过 Authentication和 OAuth2Request构造出 OAuth2Authentication + OAuth2Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request, authentication); + + // 7. 通过 AuthorizationServerTokenServices 生成 OAuth2AccessToken + OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(auth2Authentication); + + // 8. 返回 Token + log.info("登录成功"); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write(new ObjectMapper().writeValueAsString(token)); + } + + private String[] extractAndDecodeHeader(String header, HttpServletRequest request) { + byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8); + + byte[] decoded; + try { + decoded = Base64.getDecoder().decode(base64Token); + } catch (IllegalArgumentException var7) { + throw new BadCredentialsException("Failed to decode basic authentication token"); + } + + String token = new String(decoded, StandardCharsets.UTF_8); + int delim = token.indexOf(":"); + if (delim == -1) { + throw new BadCredentialsException("Invalid basic authentication token"); + } else { + return new String[]{token.substring(0, delim), token.substring(delim + 1)}; + } + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/service/RedisCodeService.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/service/RedisCodeService.java new file mode 100644 index 0000000..c512d72 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/service/RedisCodeService.java @@ -0,0 +1,60 @@ +package cc.mrbird.security.service; + +import cc.mrbird.security.validate.smscode.SmsCode; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.ServletWebRequest; + +import java.util.concurrent.TimeUnit; + +/** + * Redis操作验证码服务 + */ +@Service +public class RedisCodeService { + + private final static String SMS_CODE_PREFIX = "SMS_CODE:"; + private final static Integer TIME_OUT = 300; + + @Autowired + private StringRedisTemplate redisTemplate; + + /** + * 保存验证码到 redis + * + * @param smsCode 短信验证码 + * @param request ServletWebRequest + */ + public void save(SmsCode smsCode, ServletWebRequest request, String mobile) throws Exception { + redisTemplate.opsForValue().set(key(request, mobile), smsCode.getCode(), TIME_OUT, TimeUnit.SECONDS); + } + + /** + * 获取验证码 + * + * @param request ServletWebRequest + * @return 验证码 + */ + public String get(ServletWebRequest request, String mobile) throws Exception { + return redisTemplate.opsForValue().get(key(request, mobile)); + } + + /** + * 移除验证码 + * + * @param request ServletWebRequest + */ + public void remove(ServletWebRequest request, String mobile) throws Exception { + redisTemplate.delete(key(request, mobile)); + } + + private String key(ServletWebRequest request, String mobile) throws Exception { + String deviceId = request.getHeader("deviceId"); + if (StringUtils.isBlank(deviceId)) { + throw new Exception("请在请求头中设置deviceId"); + } + return SMS_CODE_PREFIX + deviceId + ":" + mobile; + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/service/UserDetailService.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/service/UserDetailService.java new file mode 100644 index 0000000..c1c6521 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/service/UserDetailService.java @@ -0,0 +1,43 @@ +package cc.mrbird.security.service; + +import cc.mrbird.security.domain.MyUser; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +public class UserDetailService implements UserDetailsService { + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + // 模拟一个用户,替代数据库获取逻辑 + MyUser user = new MyUser(); + user.setUserName(username); + user.setPassword(this.passwordEncoder.encode("123456")); + // 输出加密后的密码 + System.out.println(user.getPassword()); + + List authorities = new ArrayList<>(); + if (StringUtils.equalsIgnoreCase("mrbird", username)) { + authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); + } else { + authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("test"); + } + return new User(username, user.getPassword(), user.isEnabled(), + user.isAccountNonExpired(), user.isCredentialsNonExpired(), + user.isAccountNonLocked(), authorities); + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationConfig.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationConfig.java new file mode 100644 index 0000000..56fd9fa --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationConfig.java @@ -0,0 +1,40 @@ +package cc.mrbird.security.validate.smscode; + +import cc.mrbird.security.service.UserDetailService; +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.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.stereotype.Component; + +@Component +public class SmsAuthenticationConfig extends SecurityConfigurerAdapter { + + @Autowired + private AuthenticationSuccessHandler authenticationSuccessHandler; + + @Autowired + private AuthenticationFailureHandler authenticationFailureHandler; + + @Autowired + private UserDetailService userDetailService; + + @Override + public void configure(HttpSecurity http) { + SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter(); + smsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); + smsAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler); + smsAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler); + + SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider(); + smsAuthenticationProvider.setUserDetailService(userDetailService); + + http.authenticationProvider(smsAuthenticationProvider) + .addFilterAfter(smsAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationFilter.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationFilter.java new file mode 100644 index 0000000..ded0c75 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationFilter.java @@ -0,0 +1,69 @@ +package cc.mrbird.security.validate.smscode; + +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.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter { + + public static final String MOBILE_KEY = "mobile"; + + private String mobileParameter = MOBILE_KEY; + private boolean postOnly = true; + + + public SmsAuthenticationFilter() { + super(new AntPathRequestMatcher("/login/mobile", "POST")); + } + + + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException { + if (postOnly && !request.getMethod().equals("POST")) { + throw new AuthenticationServiceException( + "Authentication method not supported: " + request.getMethod()); + } + + String mobile = obtainMobile(request); + + if (mobile == null) { + mobile = ""; + } + + mobile = mobile.trim(); + + SmsAuthenticationToken authRequest = new SmsAuthenticationToken(mobile); + + setDetails(request, authRequest); + + return this.getAuthenticationManager().authenticate(authRequest); + } + + protected String obtainMobile(HttpServletRequest request) { + return request.getParameter(mobileParameter); + } + + protected void setDetails(HttpServletRequest request, + SmsAuthenticationToken authRequest) { + authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); + } + + public void setMobileParameter(String mobileParameter) { + Assert.hasText(mobileParameter, "mobile parameter must not be empty or null"); + this.mobileParameter = mobileParameter; + } + + public void setPostOnly(boolean postOnly) { + this.postOnly = postOnly; + } + + public final String getMobileParameter() { + return mobileParameter; + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationProvider.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationProvider.java new file mode 100644 index 0000000..c8e2af5 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationProvider.java @@ -0,0 +1,41 @@ +package cc.mrbird.security.validate.smscode; + +import cc.mrbird.security.service.UserDetailService; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; + +public class SmsAuthenticationProvider implements AuthenticationProvider { + + private UserDetailService userDetailService; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + SmsAuthenticationToken authenticationToken = (SmsAuthenticationToken) authentication; + UserDetails userDetails = userDetailService.loadUserByUsername((String) authenticationToken.getPrincipal()); + + if (userDetails == null) + throw new InternalAuthenticationServiceException("未找到与该手机号对应的用户"); + + SmsAuthenticationToken authenticationResult = new SmsAuthenticationToken(userDetails, userDetails.getAuthorities()); + + authenticationResult.setDetails(authenticationToken.getDetails()); + + return authenticationResult; + } + + @Override + public boolean supports(Class aClass) { + return SmsAuthenticationToken.class.isAssignableFrom(aClass); + } + + public UserDetailService getUserDetailService() { + return userDetailService; + } + + public void setUserDetailService(UserDetailService userDetailService) { + this.userDetailService = userDetailService; + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationToken.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationToken.java new file mode 100644 index 0000000..e9a9387 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsAuthenticationToken.java @@ -0,0 +1,49 @@ +package cc.mrbird.security.validate.smscode; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.SpringSecurityCoreVersion; + +import java.util.Collection; + +public class SmsAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; + + private final Object principal; + + public SmsAuthenticationToken(String mobile) { + super(null); + this.principal = mobile; + setAuthenticated(false); + } + + public SmsAuthenticationToken(Object principal, Collection authorities) { + super(authorities); + this.principal = principal; + super.setAuthenticated(true); // must use super, as we override + } + + @Override + public Object getCredentials() { + return null; + } + + public Object getPrincipal() { + return this.principal; + } + + 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/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCode.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCode.java new file mode 100644 index 0000000..9eb4b20 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCode.java @@ -0,0 +1,18 @@ +package cc.mrbird.security.validate.smscode; + +public class SmsCode { + + private String code; + + public SmsCode(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCodeFilter.java b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCodeFilter.java new file mode 100644 index 0000000..71d09e7 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCodeFilter.java @@ -0,0 +1,60 @@ +package cc.mrbird.security.validate.smscode; + +import cc.mrbird.security.service.RedisCodeService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.ServletRequestUtils; +import org.springframework.web.context.request.ServletWebRequest; +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; + +@Component +public class SmsCodeFilter extends OncePerRequestFilter { + + @Autowired + private AuthenticationFailureHandler authenticationFailureHandler; + @Autowired + private RedisCodeService redisCodeService; + + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { + if (StringUtils.equalsIgnoreCase("/login/mobile", httpServletRequest.getRequestURI()) + && StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")) { + try { + validateCode(new ServletWebRequest(httpServletRequest)); + } catch (Exception e) { + authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, new AuthenticationServiceException(e.getMessage())); + return; + } + } + filterChain.doFilter(httpServletRequest, httpServletResponse); + } + + private void validateCode(ServletWebRequest servletWebRequest) throws Exception { + String smsCodeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "smsCode"); + String mobileInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "mobile"); + + String codeInRedis = redisCodeService.get(servletWebRequest, mobileInRequest); + + if (StringUtils.isBlank(smsCodeInRequest)) { + throw new Exception("验证码不能为空!"); + } + if (codeInRedis == null) { + throw new Exception("验证码已过期!"); + } + if (!StringUtils.equalsIgnoreCase(codeInRedis, smsCodeInRequest)) { + throw new Exception("验证码不正确!"); + } + redisCodeService.remove(servletWebRequest, mobileInRequest); + + } +} \ No newline at end of file diff --git a/65.Spring-Security-OAuth2-Config/src/main/resources/application.yml b/65.Spring-Security-OAuth2-Config/src/main/resources/application.yml new file mode 100644 index 0000000..940a175 --- /dev/null +++ b/65.Spring-Security-OAuth2-Config/src/main/resources/application.yml @@ -0,0 +1,6 @@ +security: + oauth2: + client: + client-id: test + client-secret: test1234 +