From f23806a82be7bebbcae8338895cf65227e6a1b10 Mon Sep 17 00:00:00 2001 From: mrbird <852252810@qq.com> Date: Fri, 14 Sep 2018 09:05:11 +0800 Subject: [PATCH] =?UTF-8?q?Spring=20Security=E7=9F=AD=E4=BF=A1=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E7=A0=81=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 38.Spring-Security-SmsCode/pom.xml | 60 +++++++++++ .../java/cc/mrbird/SecurityApplication.java | 12 +++ .../main/java/cc/mrbird/domain/MyUser.java | 67 +++++++++++++ .../MyAuthenticationFailureHandler.java | 29 ++++++ .../MyAuthenticationSucessHandler.java | 40 ++++++++ .../browser/BrowserSecurityConfig.java | 62 ++++++++++++ .../security/browser/UserDetailService.java | 32 ++++++ .../cc/mrbird/validate/code/ImageCode.java | 53 ++++++++++ .../validate/code/ValidateCodeException.java | 11 +++ .../validate/code/ValidateCodeFilter.java | 64 ++++++++++++ .../smscode/SmsAuthenticationConfig.java | 40 ++++++++ .../smscode/SmsAuthenticationFilter.java | 69 +++++++++++++ .../smscode/SmsAuthenticationProvider.java | 41 ++++++++ .../smscode/SmsAuthenticationToken.java | 49 +++++++++ .../cc/mrbird/validate/smscode/SmsCode.java | 40 ++++++++ .../validate/smscode/SmsCodeFilter.java | 66 +++++++++++++ .../controller/BrowserSecurityController.java | 39 ++++++++ .../mrbird/web/controller/TestController.java | 20 ++++ .../web/controller/ValidateController.java | 99 +++++++++++++++++++ .../src/main/resources/application.yml | 3 + .../main/resources/resources/css/login.css | 97 ++++++++++++++++++ .../src/main/resources/resources/login.html | 34 +++++++ 22 files changed, 1027 insertions(+) create mode 100644 38.Spring-Security-SmsCode/pom.xml create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/SecurityApplication.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/domain/MyUser.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/handler/MyAuthenticationFailureHandler.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/handler/MyAuthenticationSucessHandler.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/security/browser/UserDetailService.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ImageCode.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ValidateCodeException.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationConfig.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationFilter.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationProvider.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationToken.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsCode.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsCodeFilter.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/TestController.java create mode 100644 38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/ValidateController.java create mode 100644 38.Spring-Security-SmsCode/src/main/resources/application.yml create mode 100644 38.Spring-Security-SmsCode/src/main/resources/resources/css/login.css create mode 100644 38.Spring-Security-SmsCode/src/main/resources/resources/login.html diff --git a/38.Spring-Security-SmsCode/pom.xml b/38.Spring-Security-SmsCode/pom.xml new file mode 100644 index 0000000..5b9ce7a --- /dev/null +++ b/38.Spring-Security-SmsCode/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + cc.mrbird + Security + 1.0-SNAPSHOT + jar + + Security + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.5.14.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.social + spring-social-config + + + + org.apache.commons + commons-lang3 + 3.7 + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/SecurityApplication.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/SecurityApplication.java new file mode 100644 index 0000000..4740934 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/SecurityApplication.java @@ -0,0 +1,12 @@ +package cc.mrbird; + +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/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/domain/MyUser.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/domain/MyUser.java new file mode 100644 index 0000000..dee4f7f --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/domain/MyUser.java @@ -0,0 +1,67 @@ +package cc.mrbird.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/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/handler/MyAuthenticationFailureHandler.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/handler/MyAuthenticationFailureHandler.java new file mode 100644 index 0000000..22127b4 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/handler/MyAuthenticationFailureHandler.java @@ -0,0 +1,29 @@ +package cc.mrbird.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +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 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/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/handler/MyAuthenticationSucessHandler.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/handler/MyAuthenticationSucessHandler.java new file mode 100644 index 0000000..8dc29d3 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/handler/MyAuthenticationSucessHandler.java @@ -0,0 +1,40 @@ +package cc.mrbird.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.savedrequest.SavedRequest; +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 MyAuthenticationSucessHandler implements AuthenticationSuccessHandler { + + // private RequestCache requestCache = new HttpSessionRequestCache(); + + private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + // + // @Autowired + // private ObjectMapper mapper; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + // response.setContentType("application/json;charset=utf-8"); + // response.getWriter().write(mapper.writeValueAsString(authentication)); + // SavedRequest savedRequest = requestCache.getRequest(request, response); + // System.out.println(savedRequest.getRedirectUrl()); + // redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl()); + redirectStrategy.sendRedirect(request, response, "/index"); + } +} diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java new file mode 100644 index 0000000..f230154 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java @@ -0,0 +1,62 @@ +package cc.mrbird.security.browser; + +import cc.mrbird.handler.MyAuthenticationFailureHandler; +import cc.mrbird.handler.MyAuthenticationSucessHandler; +import cc.mrbird.validate.code.ValidateCodeFilter; +import cc.mrbird.validate.smscode.SmsAuthenticationConfig; +import cc.mrbird.validate.smscode.SmsAuthenticationFilter; +import cc.mrbird.validate.smscode.SmsCodeFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private MyAuthenticationSucessHandler authenticationSucessHandler; + + @Autowired + private MyAuthenticationFailureHandler authenticationFailureHandler; + + @Autowired + private ValidateCodeFilter validateCodeFilter; + + @Autowired + private SmsCodeFilter smsCodeFilter; + + @Autowired + private SmsAuthenticationConfig smsAuthenticationConfig; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加验证码校验过滤器 + .addFilterBefore(smsCodeFilter,UsernamePasswordAuthenticationFilter.class) // 添加短信验证码校验过滤器 + .formLogin() // 表单登录 + // http.httpBasic() // HTTP Basic + .loginPage("/authentication/require") // 登录跳转 URL + .loginProcessingUrl("/login") // 处理表单登录 URL + .successHandler(authenticationSucessHandler) // 处理登录成功 + .failureHandler(authenticationFailureHandler) // 处理登录失败 + .and() + .authorizeRequests() // 授权配置 + .antMatchers("/authentication/require", + "/login.html", "/code/image","/code/sms").permitAll() // 无需认证的请求路径 + .anyRequest() // 所有请求 + .authenticated() // 都需要认证 + .and() + .csrf().disable() + .apply(smsAuthenticationConfig); // 将短信验证码认证配置加到 Spring Security 中 + } +} diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/security/browser/UserDetailService.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/security/browser/UserDetailService.java new file mode 100644 index 0000000..58992aa --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/security/browser/UserDetailService.java @@ -0,0 +1,32 @@ +package cc.mrbird.security.browser; + +import cc.mrbird.domain.MyUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +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; + +@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()); + + return new User(username, user.getPassword(), user.isEnabled(), + user.isAccountNonExpired(), user.isCredentialsNonExpired(), + user.isAccountNonLocked(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); + } +} diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ImageCode.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ImageCode.java new file mode 100644 index 0000000..28850b8 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ImageCode.java @@ -0,0 +1,53 @@ +package cc.mrbird.validate.code; + +import java.awt.image.BufferedImage; +import java.time.LocalDateTime; + +public class ImageCode { + + private BufferedImage image; + + private String code; + + private LocalDateTime expireTime; + + public ImageCode(BufferedImage image, String code, int expireIn) { + this.image = image; + this.code = code; + this.expireTime = LocalDateTime.now().plusSeconds(expireIn); + } + + public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) { + this.image = image; + this.code = code; + this.expireTime = expireTime; + } + + boolean isExpire() { + return LocalDateTime.now().isAfter(expireTime); + } + + public BufferedImage getImage() { + return image; + } + + public void setImage(BufferedImage image) { + this.image = image; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public LocalDateTime getExpireTime() { + return expireTime; + } + + public void setExpireTime(LocalDateTime expireTime) { + this.expireTime = expireTime; + } +} diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ValidateCodeException.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ValidateCodeException.java new file mode 100644 index 0000000..f958f66 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ValidateCodeException.java @@ -0,0 +1,11 @@ +package cc.mrbird.validate.code; + +import org.springframework.security.core.AuthenticationException; + +public class ValidateCodeException extends AuthenticationException { + private static final long serialVersionUID = 5022575393500654458L; + + public ValidateCodeException(String message) { + super(message); + } +} diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java new file mode 100644 index 0000000..dcf557a --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java @@ -0,0 +1,64 @@ +package cc.mrbird.validate.code; + +import cc.mrbird.web.controller.ValidateController; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.social.connect.web.HttpSessionSessionStrategy; +import org.springframework.social.connect.web.SessionStrategy; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.ServletRequestBindingException; +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 ValidateCodeFilter extends OncePerRequestFilter { + + @Autowired + private AuthenticationFailureHandler authenticationFailureHandler; + + private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { + if (StringUtils.equalsIgnoreCase("/login", httpServletRequest.getRequestURI()) + && StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")) { + try { + validateCode(new ServletWebRequest(httpServletRequest)); + } catch (ValidateCodeException e) { + authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e); + return; + } + } + filterChain.doFilter(httpServletRequest, httpServletResponse); + } + + private void validateCode(ServletWebRequest servletWebRequest) throws ServletRequestBindingException { + ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(servletWebRequest, ValidateController.SESSION_KEY_IMAGE_CODE); + String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "imageCode"); + + if (StringUtils.isBlank(codeInRequest)) { + throw new ValidateCodeException("验证码不能为空!"); + } + if (codeInSession == null) { + throw new ValidateCodeException("验证码不存在!"); + } + if (codeInSession.isExpire()) { + sessionStrategy.removeAttribute(servletWebRequest, ValidateController.SESSION_KEY_IMAGE_CODE); + throw new ValidateCodeException("验证码已过期!"); + } + if (!StringUtils.equalsIgnoreCase(codeInSession.getCode(), codeInRequest)) { + throw new ValidateCodeException("验证码不正确!"); + } + sessionStrategy.removeAttribute(servletWebRequest, ValidateController.SESSION_KEY_IMAGE_CODE); + + } + +} diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationConfig.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationConfig.java new file mode 100644 index 0000000..b76d931 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationConfig.java @@ -0,0 +1,40 @@ +package cc.mrbird.validate.smscode; + +import cc.mrbird.security.browser.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) throws Exception { + 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/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationFilter.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationFilter.java new file mode 100644 index 0000000..249f3be --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationFilter.java @@ -0,0 +1,69 @@ +package cc.mrbird.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/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationProvider.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationProvider.java new file mode 100644 index 0000000..e15b04c --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationProvider.java @@ -0,0 +1,41 @@ +package cc.mrbird.validate.smscode; + +import cc.mrbird.security.browser.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/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationToken.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationToken.java new file mode 100644 index 0000000..f33670c --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationToken.java @@ -0,0 +1,49 @@ +package cc.mrbird.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/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsCode.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsCode.java new file mode 100644 index 0000000..537135e --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsCode.java @@ -0,0 +1,40 @@ +package cc.mrbird.validate.smscode; + +import java.time.LocalDateTime; + +public class SmsCode { + + private String code; + + private LocalDateTime expireTime; + + public SmsCode(String code, int expireIn) { + this.code = code; + this.expireTime = LocalDateTime.now().plusSeconds(expireIn); + } + + public SmsCode(String code, LocalDateTime expireTime) { + this.code = code; + this.expireTime = expireTime; + } + + boolean isExpire() { + return LocalDateTime.now().isAfter(expireTime); + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public LocalDateTime getExpireTime() { + return expireTime; + } + + public void setExpireTime(LocalDateTime expireTime) { + this.expireTime = expireTime; + } +} diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsCodeFilter.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsCodeFilter.java new file mode 100644 index 0000000..c7f6499 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/validate/smscode/SmsCodeFilter.java @@ -0,0 +1,66 @@ +package cc.mrbird.validate.smscode; + +import cc.mrbird.validate.code.ValidateCodeException; +import cc.mrbird.web.controller.ValidateController; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.social.connect.web.HttpSessionSessionStrategy; +import org.springframework.social.connect.web.SessionStrategy; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.ServletRequestBindingException; +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; + + private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); + + @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 (ValidateCodeException e) { + authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e); + return; + } + } + filterChain.doFilter(httpServletRequest, httpServletResponse); + } + + private void validateCode(ServletWebRequest servletWebRequest) throws ServletRequestBindingException { + String smsCodeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "smsCode"); + String mobileInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "smsCode"); + + SmsCode codeInSession = (SmsCode) sessionStrategy.getAttribute(servletWebRequest, ValidateController.SESSION_KEY_SMS_CODE + mobileInRequest); + + if (StringUtils.isBlank(smsCodeInRequest)) { + throw new ValidateCodeException("验证码不能为空!"); + } + if (codeInSession == null) { + throw new ValidateCodeException("验证码不存在!"); + } + if (codeInSession.isExpire()) { + sessionStrategy.removeAttribute(servletWebRequest, ValidateController.SESSION_KEY_IMAGE_CODE); + throw new ValidateCodeException("验证码已过期!"); + } + if (!StringUtils.equalsIgnoreCase(codeInSession.getCode(), smsCodeInRequest)) { + throw new ValidateCodeException("验证码不正确!"); + } + sessionStrategy.removeAttribute(servletWebRequest, ValidateController.SESSION_KEY_IMAGE_CODE); + + } +} \ No newline at end of file diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java new file mode 100644 index 0000000..b198589 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java @@ -0,0 +1,39 @@ +package cc.mrbird.web.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.savedrequest.SavedRequest; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author MrBird + */ +@RestController +public class BrowserSecurityController { + + private RequestCache requestCache = new HttpSessionRequestCache(); + + private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + + @GetMapping("/authentication/require") + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public String requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException { + SavedRequest savedRequest = requestCache.getRequest(request, response); + if (savedRequest != null) { + String targetUrl = savedRequest.getRedirectUrl(); + if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) + redirectStrategy.sendRedirect(request, response, "/login.html"); + } + return "访问的资源需要身份认证!"; + } +} diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/TestController.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/TestController.java new file mode 100644 index 0000000..b677e61 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/TestController.java @@ -0,0 +1,20 @@ +package cc.mrbird.web.controller; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + @GetMapping("hello") + public String hello() { + return "hello spring security"; + } + + @GetMapping("index") + public Object index(Authentication authentication) { + // return SecurityContextHolder.getContext().getAuthentication(); + return authentication; + } +} diff --git a/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/ValidateController.java b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/ValidateController.java new file mode 100644 index 0000000..f5a8e1a --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/java/cc/mrbird/web/controller/ValidateController.java @@ -0,0 +1,99 @@ +package cc.mrbird.web.controller; + +import cc.mrbird.validate.code.ImageCode; +import cc.mrbird.validate.smscode.SmsCode; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.social.connect.web.HttpSessionSessionStrategy; +import org.springframework.social.connect.web.SessionStrategy; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.ServletWebRequest; + +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Random; + +@RestController +public class ValidateController { + + public final static String SESSION_KEY_IMAGE_CODE = "SESSION_KEY_IMAGE_CODE"; + + public final static String SESSION_KEY_SMS_CODE = "SESSION_KEY_SMS_CODE"; + + private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); + + @GetMapping("/code/image") + public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { + ImageCode imageCode = createImageCode(); + sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY_IMAGE_CODE, imageCode); + ImageIO.write(imageCode.getImage(), "jpeg", response.getOutputStream()); + } + + @GetMapping("/code/sms") + public void createSmsCode(HttpServletRequest request, HttpServletResponse response, String mobile) { + SmsCode smsCode = createSMSCode(); + sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY_SMS_CODE + mobile, smsCode); + // 输出验证码到控制台代替短信发送服务 + System.out.println("您的登录验证码为:" + smsCode.getCode() + ",有效时间为60秒"); + } + + private SmsCode createSMSCode() { + String code = RandomStringUtils.randomNumeric(6); + return new SmsCode(code, 60); + } + + private ImageCode createImageCode() { + int width = 100; // 验证码图片宽度 + int height = 36; // 验证码图片长度 + int length = 4; // 验证码位数 + int expireIn = 60; // 验证码有效时间 60s + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + Graphics g = image.getGraphics(); + + Random random = new Random(); + + g.setColor(getRandColor(200, 250)); + g.fillRect(0, 0, width, height); + g.setFont(new Font("Times New Roman", Font.ITALIC, 20)); + g.setColor(getRandColor(160, 200)); + for (int i = 0; i < 155; i++) { + int x = random.nextInt(width); + int y = random.nextInt(height); + int xl = random.nextInt(12); + int yl = random.nextInt(12); + g.drawLine(x, y, x + xl, y + yl); + } + + StringBuilder sRand = new StringBuilder(); + for (int i = 0; i < length; i++) { + String rand = String.valueOf(random.nextInt(10)); + sRand.append(rand); + g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110))); + g.drawString(rand, 13 * i + 6, 16); + } + + g.dispose(); + + return new ImageCode(image, sRand.toString(), expireIn); + } + + private Color getRandColor(int fc, int bc) { + Random random = new Random(); + if (fc > 255) + fc = 255; + + if (bc > 255) + bc = 255; + int r = fc + random.nextInt(bc - fc); + int g = fc + random.nextInt(bc - fc); + int b = fc + random.nextInt(bc - fc); + return new Color(r, g, b); + } + +} diff --git a/38.Spring-Security-SmsCode/src/main/resources/application.yml b/38.Spring-Security-SmsCode/src/main/resources/application.yml new file mode 100644 index 0000000..a618ee1 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/resources/application.yml @@ -0,0 +1,3 @@ +security: + basic: + enabled: true \ No newline at end of file diff --git a/38.Spring-Security-SmsCode/src/main/resources/resources/css/login.css b/38.Spring-Security-SmsCode/src/main/resources/resources/css/login.css new file mode 100644 index 0000000..52d1699 --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/resources/resources/css/login.css @@ -0,0 +1,97 @@ +.login-page { + width: 360px; + padding: 8% 0 0; + margin: auto; +} +.form { + position: relative; + z-index: 1; + background: #ffffff; + max-width: 360px; + margin: 0 auto 100px; + padding: 45px; + text-align: center; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); +} +.form input { + outline: 0; + background: #f2f2f2; + width: 100%; + border: 0; + margin: 0 0 15px; + padding: 15px; + box-sizing: border-box; + font-size: 14px; +} +.form button { + text-transform: uppercase; + outline: 0; + background: #4caf50; + width: 100%; + border: 0; + padding: 15px; + color: #ffffff; + font-size: 14px; + -webkit-transition: all 0.3 ease; + transition: all 0.3 ease; + cursor: pointer; +} +.form button:hover, +.form button:active, +.form button:focus { + background: #43a047; +} +.form .message { + margin: 15px 0 0; + color: #b3b3b3; + font-size: 12px; +} +.form .message a { + color: #4caf50; + text-decoration: none; +} +.form .register-form { + display: none; +} +.container { + position: relative; + z-index: 1; + max-width: 300px; + margin: 0 auto; +} +.container:before, +.container:after { + content: ""; + display: block; + clear: both; +} +.container .info { + margin: 50px auto; + text-align: center; +} +.container .info h1 { + margin: 0 0 15px; + padding: 0; + font-size: 36px; + font-weight: 300; + color: #1a1a1a; +} +.container .info span { + color: #4d4d4d; + font-size: 12px; +} +.container .info span a { + color: #000000; + text-decoration: none; +} +.container .info span .fa { + color: #ef3b3a; +} +body { + background: #76b852; /* fallback for old browsers */ + background: -webkit-linear-gradient(right, #76b852, #8dc26f); + background: -moz-linear-gradient(right, #76b852, #8dc26f); + background: -o-linear-gradient(right, #76b852, #8dc26f); + background: linear-gradient(to left, #76b852, #8dc26f); + font-family: Lato,"PingFang SC","Microsoft YaHei",sans-serif; +} diff --git a/38.Spring-Security-SmsCode/src/main/resources/resources/login.html b/38.Spring-Security-SmsCode/src/main/resources/resources/login.html new file mode 100644 index 0000000..13a9cdc --- /dev/null +++ b/38.Spring-Security-SmsCode/src/main/resources/resources/login.html @@ -0,0 +1,34 @@ + + + + + 登录 + + + +
+
+

账户登录

+ + + + + + + +
+
+ +
+
+

短信验证码登录

+ + + + 发送验证码 + + +
+
+ + \ No newline at end of file