项目结构图
build.gradle
plugins {
id 'org.springframework.boot' version '2.1.16.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'war'
}
group = 'com.sky'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenLocal()
maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter'}
maven { url 'https://repo.spring.io/snapshot' }
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://plugins.gradle.org/m2/' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test{
//useJUnitPlatform()
}
application.yml
server:
port: 8081
SpringBootGradleApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class SpringBootGradleApplication extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(SpringBootGradleApplication.class, args);
}
/** 使用外置的tomcat启动,需要继承SpringBootServletInitializer */
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringBootGradleApplication.class);
}
}
SysUser 用户信息,用于登录
mobile用于手机登录
public class SysUser {
private int id;
private String username;
private String password;
private String mobile;
private List<String> roles;
public SysUser() {}
public SysUser(int id, String username, String password,String mobile) {
this.id = id;
this.username = username;
this.password = password;
this.mobile = mobile;
}
//...省略get set
public SysUser addRole(String roleName) {
if(this.roles == null) {
this.roles = new ArrayList<>();
}
roles.add(roleName);
return this;
}
}
用户service类,构造用户数据,并实现UserDetailsService
public interface SysUserService {
/** 根据账号获取用户 */
SysUser getUserByAccount(String account);
/** 根据电话获取用户 */
SysUser getUserByMobile(String mobile);
}
@Service
public class SysUserServiceImpl implements SysUserService , UserDetailsService {
private static Map<String, SysUser> USER_MAP = new HashMap<>(16);
private static Map<String, String> USER_MODEL_MAP = new HashMap<>(16);
static{
//密码都是123456
String encodePass = "{bcrypt}$2a$10$t8D4CmkVOCx834key9Mz5OL0M3MJlgd2c4hV3dxfl4zTXgJG1nJu.";
USER_MAP.put("admin", new SysUser(1,"admin",encodePass,"19935588101").addRole("ROLE_EMPLOYEE").addRole("ROLE_ADMIN"));
USER_MODEL_MAP.put("19935588101", "admin");
USER_MAP.put("lisa", new SysUser(2,"lisa",encodePass,"19935588102").addRole("ROLE_EMPLOYEE"));
USER_MODEL_MAP.put("19935588102", "lisa");
USER_MAP.put("zhaosi", new SysUser(3,"zhaosi",encodePass,"19935588103"));
USER_MODEL_MAP.put("19935588103", "zhaosi");
}
@Override
public SysUser getUserByAccount(String account) {
return USER_MAP.get(account);
}
@Override
public SysUser getUserByMobile(String mobile) {
String account = USER_MODEL_MAP.get(mobile);
return getUserByAccount(account);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StringUtils.isEmpty(username)) {
throw new UsernameNotFoundException("UserDetailsService没有接收到用户账号");
}
SysUser sysUser = getUserByAccount(username);
if (sysUser == null) {
throw new UsernameNotFoundException(String.format("用户'%s'不存在", username));
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (String role : sysUser.getRoles()) {
// 封装用户信息和角色信息到SecurityContextHolder全局缓存中
grantedAuthorities.add(new SimpleGrantedAuthority(role));
}
return new User(sysUser.getUsername(), sysUser.getPassword(), grantedAuthorities);
}
}
SecurityConfig:spring security的配置类
@Configuration
@EnableWebSecurity
@Order(1) // 如果配置了多个WebSecurityConfigurerAdapter
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Autowired
private AuthenticationManager authenticationManager;
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Autowired
private UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider;
@Autowired
private MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(mobileCodeAuthenticationProvider)
.authenticationProvider(usernamePasswordAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 匹配的资源任何人都可以访问
http.authorizeRequests().antMatchers("/", "/home").permitAll();
// 访问任何资源都需要通过身份验证
http.authorizeRequests().anyRequest().authenticated();
// 指定登录页面,任何人都可以访问
http.authorizeRequests().and().formLogin()
.loginPage("/login")// 指定登录页面, spring security有默认的登录页面
.permitAll().and();
// 指定登出页面,任何人都可以访问
http.authorizeRequests().and().logout().permitAll().and();
//增加过滤器
http.addFilterBefore(mobileCodeAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public MobileCodeAuthenticationProcessingFilter mobileCodeAuthenticationProcessingFilter() {
MobileCodeAuthenticationProcessingFilter filter = new MobileCodeAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
}
用户名密码认证
UsernamePasswordAuthenticationProvider
/** 用户名密码认证 */
@Component
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
@Autowired UserDetailsService userDetailsService;
@Autowired PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
String password = (String) authentication.getCredentials();
if (!StringUtils.hasText(password)) {
throw new BadCredentialsException("密码不能为空");
}
UserDetails user = userDetailsService.loadUserByUsername(username);
if (null == user) {
throw new BadCredentialsException("用户不存在");
}
//校验密码
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("用户名或密码不正确");
}
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
@Override
public boolean supports(Class<?> authentication) {
boolean flag = UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
System.out.println(this.getClass().getName() + "---supports:" + flag);
return flag;
}
}
手机验证码认证
MobileCodeAuthenticationProcessingFilter
MobileCodeAuthenticationToken
MobileCodeAuthenticationProvider
public class MobileCodeAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
public static final String SPRING_SECURITY_FORM_CODE_KEY = "code";
private boolean postOnly = true;
public MobileCodeAuthenticationProcessingFilter() {
super(new AntPathRequestMatcher("/mobileCodeLogin", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String mobile = getParamter(request, SPRING_SECURITY_FORM_MOBILE_KEY);
String code = getParamter(request, SPRING_SECURITY_FORM_CODE_KEY);
AbstractAuthenticationToken authRequest = new MobileCodeAuthenticationToken(mobile, code);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String getParamter(HttpServletRequest request, String paramterName) {
String value = request.getParameter(paramterName);
if(value == null) {
return "";
}
return value.trim();
}
protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
public class MobileCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private String credentials;
public MobileCodeAuthenticationToken(Object principal, String credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public MobileCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = null;
super.setAuthenticated(true); // must use super, as we override
}
public String getCredentials() {
return this.credentials;
}
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();
credentials = null;
}
}
@Component
public class MobileCodeAuthenticationProvider implements AuthenticationProvider {
@Autowired UserDetailsService userDetailsService;
@Autowired SysUserService sysUserService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String mobile = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
String code = (String) authentication.getCredentials();
if (!StringUtils.hasText(code)) {
throw new BadCredentialsException("验证码不能为空");
}
SysUser sysUser = sysUserService.getUserByMobile(mobile);
if (null == sysUser) {
throw new BadCredentialsException("用户不存在");
}
UserDetails user = userDetailsService.loadUserByUsername(sysUser.getUsername());
if (null == user) {
throw new BadCredentialsException("用户不存在");
}
// 手机号验证码业务还没有开发,先用4个0验证
if (!code.equals("0000")) {
throw new BadCredentialsException("验证码不正确");
}
MobileCodeAuthenticationToken result = new MobileCodeAuthenticationToken(user.getUsername(), user.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
@Override
public boolean supports(Class<?> authentication) {
boolean flag = MobileCodeAuthenticationToken.class.isAssignableFrom(authentication);
System.out.println(this.getClass().getName() + "---supports:" + flag);
return flag;
}
}
MvcConfig
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/ignore").setViewName("ignore");
}
}
相关html页面
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title> login.html </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<h1>账号登录</h1>
<form th:action="@{ /login }" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
<h1>手机登录登录</h1>
<form th:action="@{ /mobileCodeLogin }" method="post">
<div><label> mobile number : <input type="text" name="mobile"/> </label></div>
<div><label> code: <input type="text" name="code"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>home.html</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
</body>
</html>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>hello.html</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
<br/>
</body>
</html>
测试
分别登录,成功!