首先參照官方文檔,我們構建一個 SpringBoot Security . 本文以Maven構建.粘貼即可
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>securitytest</groupId>
<artifactId>securitytest</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
</dependencies>
</project>
Java Config Mvc Security Application
package com.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
package com.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(new UserDetailsService() {
// @Override
// public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// return null;
// }
// })
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user").
password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
}
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) throws Throwable {
SpringApplication.run(Application.class, args);
}
}
其中注意 WebSecurityConfig 有三種config方法
// 此方法控制主要是作用爲用戶默認,識別你是否爲本系統用戶
protected void configure(AuthenticationManagerBuilder auth) throws Exception;
// 此方法主要保護訪問內部資源
protected void configure(HttpSecurity http) throws Exception ;
// 此方法爲全局方法,構建過濾鏈條
public void configure(WebSecurity web) throws Exception ;
Security核心有2種作用。第一 識別,你是誰,第二 你能做什麼。
Security登錄流程
首先外部系統訪問會經過一系列 WebSecurity構建的過濾鏈,最終達到其中一個
AbstractAuthenticationProcessingFilter.doFilter(req,res,chain)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 如果不需要鑑權,測通過
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
// 失敗
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
// 會話管理
sessionStrategy.onAuthentication(authResult, request, response);
}
}
我們研究的主題方法,
`attemptAuthentication(req,res) `,登錄方法
`onAuthentication(authResult, request, response)` 會話管理,二次登錄方法
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 username = obtainUsername(request); // request.getParamter("username")
String password = obtainPassword(request); //同上
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// getAuthenticationManager 此處是一個認證管理器。是上面auth創出 ProviderManager
return this.getAuthenticationManager().authenticate(authRequest);
}
// 此處是上面auth配置的默認認證器
public class ProviderManager{
...
// 獲取所有的認證器服務。此服務可以創建時指定。默認匿名服務
public List<AuthenticationProvider> getProviders() {
return providers;
}
}
通過這些鑑權器可以完成所有協議的支持.
我們重點研究 authenticate(authentication) 方法
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
}
}
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
// 密碼校驗 passwordEncoder 可自定義
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
最終會調用我們設置的UserDetailService。中loadUserByUsername。獲取用戶。我們自定義和實現基於jdbc的設置.然後通過密碼校驗。完成登錄.
登錄成功後 放入
SecurityContextHolder.getContext中
在第二次訪問中。調用 SecurityContextPersistenceFilter.doFilter(res,req,chain)
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain){
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
SecurityContextHolder.setContext(contextBeforeChainExecution);
}
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
}
Tomcat,中
Session doGetSession(boolean create)
如果找到就進行識別