1. `pom`文件
```xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
<properties>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
```
2. `application.yml`文件
```yaml
server:
port: 80
```
3. 入口类
```java
@SpringBootApplication
public class SpringSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
```
4. 登录页`resources/static/login.html`
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/login/mobile" method="post">
<div class="form">
<h3>短信验证码登录</h3>
<input type="text" placeholder="手机号" name="mobile" value="13688125555" required="required"/>
<input type="text" name="smsCode" placeholder="短信验证码"/>
<a href="/code/sms?mobile=13688125555">发送验证码</a>
<button type="submit">登录</button>
</div>
</form>
</body>
</html>
```
5. 控制器(资源)
```java
@RestController
public class HomeController {
@GetMapping("/")
public String home() {
return "hello spring security";
}
}
```
6. 控制器(发送验证码)
```java
@RestController
public class ValidateController {
public final static String SESSION_KEY_SMS_CODE = "SESSION_KEY_SMS_CODE";
@GetMapping("/code/sms")
public void createSmsCode(HttpServletRequest request, HttpServletResponse response, String mobile) throws IOException {
SmsCode smsCode = createSMSCode();
request.getSession().setAttribute(SESSION_KEY_SMS_CODE + mobile, smsCode);
sendSms(smsCode.getCode());
}
private SmsCode createSMSCode() {
String code = "666666";
return new SmsCode(code, 60);
}
private void sendSms(String code) {
System.out.println("您的登录验证码为:" + code + ",有效时间为60秒");
}
}
```
7. 短信类
```java
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;
}
}
```
8. 短信认证类
```java
public class SmsAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
public SmsAuthenticationToken(Object principal) {
super(null);
this.principal = principal;
setAuthenticated(false);
}
public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@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();
}
}
```
9. UserDetailsService
```java
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("user",
passwordEncoder.encode("user"),
Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
}
}
```
10. 校验短信验证码`SmsCodeFilter`
```java
@Component
public class SmsCodeFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
// TODO 校验code 根据请求url,判断是否是短信登录请求
System.out.println("校验code...");
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
```
11. 短信认证过滤器
```java
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;
}
}
```
12. 短信认证提供者
```java
public class SmsAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsAuthenticationToken authenticationToken = (SmsAuthenticationToken) authentication;
UserDetails userDetails = userDetailsService.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 UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
```
13. 短信认证配置
```java
@Component
public class SmsAuthenticationConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) throws Exception {
SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter();
smsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider();
smsAuthenticationProvider.setUserDetailsService(userDetailsService);
http.authenticationProvider(smsAuthenticationProvider)
.addFilterAfter(smsAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
```
14. 总配置
```java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SmsCodeFilter smsCodeFilter;
@Autowired
private SmsAuthenticationConfig smsAuthenticationConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class);
http.apply(smsAuthenticationConfig);
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.and()
.authorizeRequests()
.antMatchers("/login.html", "/code/sms").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
```