Spring Security短信验证码登录
This commit is contained in:
parent
56fe54d1af
commit
f23806a82b
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>cc.mrbird</groupId>
|
||||||
|
<artifactId>Security</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>Security</name>
|
||||||
|
<description>Demo project for Spring Boot</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>1.5.14.RELEASE</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.social</groupId>
|
||||||
|
<artifactId>spring-social-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 中
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<DefaultSecurityFilterChain, HttpSecurity> {
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 "访问的资源需要身份认证!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
security:
|
||||||
|
basic:
|
||||||
|
enabled: true
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>登录</title>
|
||||||
|
<link rel="stylesheet" href="css/login.css" type="text/css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form class="login-page" action="/login" method="post">
|
||||||
|
<div class="form">
|
||||||
|
<h3>账户登录</h3>
|
||||||
|
<input type="text" placeholder="用户名" name="username" required="required"/>
|
||||||
|
<input type="password" placeholder="密码" name="password" required="required"/>
|
||||||
|
<span style="display: inline">
|
||||||
|
<input type="text" name="imageCode" placeholder="验证码" style="width: 50%;"/>
|
||||||
|
<img src="/code/image"/>
|
||||||
|
</span>
|
||||||
|
<button type="submit">登录</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form class="login-page" action="/login/mobile" method="post">
|
||||||
|
<div class="form">
|
||||||
|
<h3>短信验证码登录</h3>
|
||||||
|
<input type="text" placeholder="手机号" name="mobile" value="17777777777" required="required"/>
|
||||||
|
<span style="display: inline">
|
||||||
|
<input type="text" name="smsCode" placeholder="短信验证码" style="width: 50%;"/>
|
||||||
|
<a href="/code/sms?mobile=17777777777">发送验证码</a>
|
||||||
|
</span>
|
||||||
|
<button type="submit">登录</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue