From cd018179c1b2d3c5568531af31b4ff8045afd30c Mon Sep 17 00:00:00 2001
From: mrbird <852252810@qq.com>
Date: Tue, 18 Jun 2019 16:44:52 +0800
Subject: [PATCH] =?UTF-8?q?Spring=20Security=E9=80=80=E5=87=BA=E7=99=BB?=
=?UTF-8?q?=E5=BD=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
60.Spring-Security-Logout/pom.xml | 65 ++++++++++++
.../java/cc/mrbird/SecurityApplication.java | 12 +++
.../main/java/cc/mrbird/domain/MyUser.java | 67 ++++++++++++
.../MyAuthenticationFailureHandler.java | 29 +++++
.../MyAuthenticationSucessHandler.java | 40 +++++++
.../handler/MyLogOutSuccessHandler.java | 25 +++++
.../browser/BrowserSecurityConfig.java | 81 ++++++++++++++
.../security/browser/UserDetailService.java | 32 ++++++
.../session/MySessionExpiredStrategy.java | 25 +++++
.../cc/mrbird/validate/code/ImageCode.java | 55 ++++++++++
.../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 | 50 +++++++++
.../mrbird/web/controller/TestController.java | 20 ++++
.../web/controller/ValidateController.java | 100 ++++++++++++++++++
.../src/main/resources/application.yml | 11 ++
.../main/resources/resources/css/login.css | 97 +++++++++++++++++
.../src/main/resources/resources/login.html | 34 ++++++
24 files changed, 1123 insertions(+)
create mode 100644 60.Spring-Security-Logout/pom.xml
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/SecurityApplication.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/domain/MyUser.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyAuthenticationFailureHandler.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyAuthenticationSucessHandler.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyLogOutSuccessHandler.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/security/browser/UserDetailService.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/session/MySessionExpiredStrategy.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ImageCode.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ValidateCodeException.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationConfig.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationFilter.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationProvider.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationToken.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsCode.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsCodeFilter.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/TestController.java
create mode 100644 60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/ValidateController.java
create mode 100644 60.Spring-Security-Logout/src/main/resources/application.yml
create mode 100644 60.Spring-Security-Logout/src/main/resources/resources/css/login.css
create mode 100644 60.Spring-Security-Logout/src/main/resources/resources/login.html
diff --git a/60.Spring-Security-Logout/pom.xml b/60.Spring-Security-Logout/pom.xml
new file mode 100644
index 00000000..339bcb24
--- /dev/null
+++ b/60.Spring-Security-Logout/pom.xml
@@ -0,0 +1,65 @@
+
+
+ 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.session
+ spring-session
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
diff --git a/60.Spring-Security-Logout/src/main/java/cc/mrbird/SecurityApplication.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/SecurityApplication.java
new file mode 100644
index 00000000..47409349
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/domain/MyUser.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/domain/MyUser.java
new file mode 100644
index 00000000..dee4f7ff
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyAuthenticationFailureHandler.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyAuthenticationFailureHandler.java
new file mode 100644
index 00000000..22127b47
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyAuthenticationSucessHandler.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyAuthenticationSucessHandler.java
new file mode 100644
index 00000000..8dc29d39
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyLogOutSuccessHandler.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyLogOutSuccessHandler.java
new file mode 100644
index 00000000..3835aca7
--- /dev/null
+++ b/60.Spring-Security-Logout/src/main/java/cc/mrbird/handler/MyLogOutSuccessHandler.java
@@ -0,0 +1,25 @@
+package cc.mrbird.handler;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author MrBird
+ */
+@Component
+public class MyLogOutSuccessHandler implements LogoutSuccessHandler {
+
+ @Override
+ public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
+ httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
+ httpServletResponse.setContentType("application/json;charset=utf-8");
+ httpServletResponse.getWriter().write("退出成功,请重新登录");
+ }
+}
diff --git a/60.Spring-Security-Logout/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java
new file mode 100644
index 00000000..a7e7483b
--- /dev/null
+++ b/60.Spring-Security-Logout/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java
@@ -0,0 +1,81 @@
+package cc.mrbird.security.browser;
+
+import cc.mrbird.handler.MyAuthenticationFailureHandler;
+import cc.mrbird.handler.MyAuthenticationSucessHandler;
+import cc.mrbird.handler.MyLogOutSuccessHandler;
+import cc.mrbird.session.MySessionExpiredStrategy;
+import cc.mrbird.validate.code.ValidateCodeFilter;
+import cc.mrbird.validate.smscode.SmsAuthenticationConfig;
+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;
+ @Autowired
+ private MySessionExpiredStrategy sessionExpiredStrategy;
+
+ @Autowired
+ private MyLogOutSuccessHandler logOutSuccessHandler;
+
+ @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","/session/invalid", "/signout/success").permitAll() // 无需认证的请求路径
+ .anyRequest() // 所有请求
+ .authenticated() // 都需要认证
+ .and()
+ .sessionManagement() // 添加 Session管理器
+ .invalidSessionUrl("/session/invalid") // Session失效后跳转到这个链接
+ .maximumSessions(1)
+ .maxSessionsPreventsLogin(true)
+ .expiredSessionStrategy(sessionExpiredStrategy)
+ .and()
+ .and()
+ .logout()
+ .logoutUrl("/signout")
+ // .logoutSuccessUrl("/signout/success")
+ .logoutSuccessHandler(logOutSuccessHandler)
+ .deleteCookies("JSESSIONID")
+ .and()
+ .csrf().disable()
+ .apply(smsAuthenticationConfig); // 将短信验证码认证配置加到 Spring Security 中
+ }
+}
diff --git a/60.Spring-Security-Logout/src/main/java/cc/mrbird/security/browser/UserDetailService.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/security/browser/UserDetailService.java
new file mode 100644
index 00000000..58992aa1
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/session/MySessionExpiredStrategy.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/session/MySessionExpiredStrategy.java
new file mode 100644
index 00000000..3d97bc54
--- /dev/null
+++ b/60.Spring-Security-Logout/src/main/java/cc/mrbird/session/MySessionExpiredStrategy.java
@@ -0,0 +1,25 @@
+package cc.mrbird.session;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.web.session.SessionInformationExpiredEvent;
+import org.springframework.security.web.session.SessionInformationExpiredStrategy;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author MrBird
+ */
+@Component
+public class MySessionExpiredStrategy implements SessionInformationExpiredStrategy {
+
+ @Override
+ public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
+ HttpServletResponse response = event.getResponse();
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ response.setContentType("application/json;charset=utf-8");
+ response.getWriter().write("您的账号已经在别的地方登录,当前登录已失效。如果密码遭到泄露,请立即修改密码!");
+ }
+}
diff --git a/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ImageCode.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ImageCode.java
new file mode 100644
index 00000000..bd6fd377
--- /dev/null
+++ b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ImageCode.java
@@ -0,0 +1,55 @@
+package cc.mrbird.validate.code;
+
+import java.awt.image.BufferedImage;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+public class ImageCode implements Serializable {
+
+ private static final long serialVersionUID = -7831615057416168810L;
+ 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/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ValidateCodeException.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ValidateCodeException.java
new file mode 100644
index 00000000..f958f665
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java
new file mode 100644
index 00000000..dcf557a0
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationConfig.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationConfig.java
new file mode 100644
index 00000000..b76d9316
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationFilter.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationFilter.java
new file mode 100644
index 00000000..249f3bef
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationProvider.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationProvider.java
new file mode 100644
index 00000000..e15b04cd
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationToken.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsAuthenticationToken.java
new file mode 100644
index 00000000..f33670c7
--- /dev/null
+++ b/60.Spring-Security-Logout/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 extends GrantedAuthority> 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/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsCode.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsCode.java
new file mode 100644
index 00000000..537135e0
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsCodeFilter.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/validate/smscode/SmsCodeFilter.java
new file mode 100644
index 00000000..c7f64993
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java
new file mode 100644
index 00000000..a26fe88b
--- /dev/null
+++ b/60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java
@@ -0,0 +1,50 @@
+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 "访问的资源需要身份认证!";
+ }
+
+ @GetMapping("/session/invalid")
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ public String sessionInvalid() {
+ return "session已失效,请重新认证";
+ }
+
+ @GetMapping("/signout/success")
+ public String signout() {
+ return "退出成功,请重新登录";
+ }
+}
diff --git a/60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/TestController.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/TestController.java
new file mode 100644
index 00000000..b677e616
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/ValidateController.java b/60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/ValidateController.java
new file mode 100644
index 00000000..f43fe738
--- /dev/null
+++ b/60.Spring-Security-Logout/src/main/java/cc/mrbird/web/controller/ValidateController.java
@@ -0,0 +1,100 @@
+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();
+ ImageCode codeInRedis = new ImageCode(null,imageCode.getCode(),imageCode.getExpireTime());
+ sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY_IMAGE_CODE, codeInRedis);
+ 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/60.Spring-Security-Logout/src/main/resources/application.yml b/60.Spring-Security-Logout/src/main/resources/application.yml
new file mode 100644
index 00000000..e98b1648
--- /dev/null
+++ b/60.Spring-Security-Logout/src/main/resources/application.yml
@@ -0,0 +1,11 @@
+security:
+ basic:
+ enabled: true
+
+server:
+ session:
+ timeout: 3600
+
+spring:
+ session:
+ store-type: redis
\ No newline at end of file
diff --git a/60.Spring-Security-Logout/src/main/resources/resources/css/login.css b/60.Spring-Security-Logout/src/main/resources/resources/css/login.css
new file mode 100644
index 00000000..52d16991
--- /dev/null
+++ b/60.Spring-Security-Logout/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/60.Spring-Security-Logout/src/main/resources/resources/login.html b/60.Spring-Security-Logout/src/main/resources/resources/login.html
new file mode 100644
index 00000000..13a9cdc9
--- /dev/null
+++ b/60.Spring-Security-Logout/src/main/resources/resources/login.html
@@ -0,0 +1,34 @@
+
+
+
+
+ 登录
+
+
+
+
+
+
+
+
\ No newline at end of file