diff --git a/37.Spring-Security-RememberMe/pom.xml b/37.Spring-Security-RememberMe/pom.xml
new file mode 100644
index 00000000..dac82380
--- /dev/null
+++ b/37.Spring-Security-RememberMe/pom.xml
@@ -0,0 +1,68 @@
+
+
+ 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.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ mysql
+ mysql-connector-java
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.7
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
diff --git a/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/SecurityApplication.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/SecurityApplication.java
new file mode 100644
index 00000000..47409349
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/domain/MyUser.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/domain/MyUser.java
new file mode 100644
index 00000000..dee4f7ff
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/handler/MyAuthenticationFailureHandler.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/handler/MyAuthenticationFailureHandler.java
new file mode 100644
index 00000000..22127b47
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/handler/MyAuthenticationSucessHandler.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/handler/MyAuthenticationSucessHandler.java
new file mode 100644
index 00000000..8dc29d39
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java
new file mode 100644
index 00000000..a481ec93
--- /dev/null
+++ b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/security/browser/BrowserSecurityConfig.java
@@ -0,0 +1,75 @@
+package cc.mrbird.security.browser;
+
+import cc.mrbird.handler.MyAuthenticationFailureHandler;
+import cc.mrbird.handler.MyAuthenticationSucessHandler;
+import cc.mrbird.validate.code.ValidateCodeFilter;
+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;
+import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
+import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ private MyAuthenticationSucessHandler authenticationSucessHandler;
+
+ @Autowired
+ private MyAuthenticationFailureHandler authenticationFailureHandler;
+
+ @Autowired
+ private ValidateCodeFilter validateCodeFilter;
+
+ @Autowired
+ private UserDetailService userDetailService;
+
+ @Autowired
+ private DataSource dataSource;
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public PersistentTokenRepository persistentTokenRepository() {
+ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
+ jdbcTokenRepository.setDataSource(dataSource);
+ jdbcTokenRepository.setCreateTableOnStartup(false);
+ return jdbcTokenRepository;
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+
+ http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加验证码校验过滤器
+ .formLogin() // 表单登录
+ // http.httpBasic() // HTTP Basic
+ .loginPage("/authentication/require") // 登录跳转 URL
+ .loginProcessingUrl("/login") // 处理表单登录 URL
+ .successHandler(authenticationSucessHandler) // 处理登录成功
+ .failureHandler(authenticationFailureHandler) // 处理登录失败
+ .and()
+ .rememberMe()
+ .tokenRepository(persistentTokenRepository()) // 配置 token 持久化仓库
+ .tokenValiditySeconds(3600) // remember 过期时间,单为秒
+ .userDetailsService(userDetailService) // 处理自动登录逻辑
+ .and()
+ .authorizeRequests() // 授权配置
+ .antMatchers("/authentication/require",
+ "/login.html",
+ "/code/image").permitAll() // 无需认证的请求路径
+ .anyRequest() // 所有请求
+ .authenticated() // 都需要认证
+ .and()
+ .csrf().disable();
+ }
+}
diff --git a/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/security/browser/UserDetailService.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/security/browser/UserDetailService.java
new file mode 100644
index 00000000..58992aa1
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/validate/code/ImageCode.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/validate/code/ImageCode.java
new file mode 100644
index 00000000..28850b82
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/validate/code/ValidateCodeException.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/validate/code/ValidateCodeException.java
new file mode 100644
index 00000000..2ce2ceca
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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;
+
+ ValidateCodeException(String message) {
+ super(message);
+ }
+}
diff --git a/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java
new file mode 100644
index 00000000..742eafd4
--- /dev/null
+++ b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/validate/code/ValidateCodeFilter.java
@@ -0,0 +1,63 @@
+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/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/web/controller/BrowserSecurityController.java
new file mode 100644
index 00000000..b198589b
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/web/controller/TestController.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/web/controller/TestController.java
new file mode 100644
index 00000000..b677e616
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/web/controller/ValidateController.java b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/web/controller/ValidateController.java
new file mode 100644
index 00000000..be2a9d4c
--- /dev/null
+++ b/37.Spring-Security-RememberMe/src/main/java/cc/mrbird/web/controller/ValidateController.java
@@ -0,0 +1,82 @@
+package cc.mrbird.web.controller;
+
+import cc.mrbird.validate.code.ImageCode;
+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";
+
+ 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());
+ }
+
+ 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/37.Spring-Security-RememberMe/src/main/resources/application.yml b/37.Spring-Security-RememberMe/src/main/resources/application.yml
new file mode 100644
index 00000000..8d8dee06
--- /dev/null
+++ b/37.Spring-Security-RememberMe/src/main/resources/application.yml
@@ -0,0 +1,12 @@
+security:
+ basic:
+ enabled: true
+
+
+
+spring:
+ datasource:
+ driver-class-name: com.mysql.jdbc.Driver
+ url: jdbc:mysql://127.0.0.1:3306/security?useUnicode=yes&characterEncoding=UTF-8&useSSL=false
+ username: root
+ password: 123456
\ No newline at end of file
diff --git a/37.Spring-Security-RememberMe/src/main/resources/resources/css/login.css b/37.Spring-Security-RememberMe/src/main/resources/resources/css/login.css
new file mode 100644
index 00000000..52d16991
--- /dev/null
+++ b/37.Spring-Security-RememberMe/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/37.Spring-Security-RememberMe/src/main/resources/resources/login.html b/37.Spring-Security-RememberMe/src/main/resources/resources/login.html
new file mode 100644
index 00000000..75d318c4
--- /dev/null
+++ b/37.Spring-Security-RememberMe/src/main/resources/resources/login.html
@@ -0,0 +1,23 @@
+
+
+
+
+ 登录
+
+
+
+
+
+
\ No newline at end of file