一、springSecurity
1、springSecurity原理概述
springSecurity原理:简单理解为过滤器链支持不同的身份认证方式(主要比如:UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter...ExceptionTranslationFilter--捕获异常引导用户登录成功、FilterSecurityInterceptor),请求和响应都会经过过滤器然后才能到我们最终的服务,其中访问顺序绿色的可选。
image.png
2、原理概述对应代码逻辑
filter的产生是通过WebSecurityConfigurerAdapter,就是自己写的配置类继承的类(我这里是SecurityConfig),WebSecurityConfigurerAdapter里有一个初始化方法,放了一个HttpSecurity ,保证用户可以通过表单或者 http 的方式进行认证
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = this.getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = (FilterSecurityInterceptor)http.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
filter注入入口是@EnableWebSecurity,@EnableWebSecurity中import了WebSecurityConfiguration,而WebSecurityConfiguration中的springSecurityFilterChain放入filter,spring会通过DelegatingFilterProxy获取filter
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {
});
//一个add方法
this.webSecurity.apply(adapter);
}
return (Filter)this.webSecurity.build();
}
二、认证和授权
1、认证处理逻辑
AuthenticationManager 将收集到的用户信息,循环遍历AuthenticationProvider找到适合(support方法)的登录方式(比如用户密码登录、第三方登录等---图中示例的是用户密码登录的过滤器),AuthenticationProvider做相应的校验处理,其中包含调用userDetails数据库中用户信息进行校验,拿到数据库用户信息与登录信息做预检查(preAuthenticationChecks),检查userDetails中的三个方法(账户是否锁定、过期等),然后进行附加检查(additionalAuthenticationChecks),包含passwordEncoder校验当前密码是否匹配,最后进行后检查(postAuthenticationChecks),包含userDetails中最后一个方法检查,如果均验证成功会创建一个SucessAuthentication,生成已认证的Authentication,successfulAuthentication会调用登录成功的处理器(可以在handler中自定义登录成功处理器)。如果期间出现任何异常会调用unsuccessfulAuthentication,在handler里可以调自定义失败的处理器
image.pngimage.png
SecurityContextPersistenceFilter作用是检查请求里是否有securityContext,请求来时有SecurityContext则拿出来放在线程里,如果请求返回时有则拿出来放到session里(同一个线程里SecurityContextHolders.getContext随时可以拿到用户信息)
2、认证的代码逻辑
认证是通过UsernamePasswordAuthenticationFilter继承AbstractAuthenticationProcessingFilter,主要方法是doFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//不需要验证的不拦截,到下一个filter
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
//进行验证
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
this.successfulAuthentication(request, response, chain, authResult);
}
}
验证的逻辑如下,生成UsernamePasswordAuthenticationToken ,UsernamePasswordAuthenticationToken 继承了AbstractAuthenticationToken,可以理解为UsernamePasswordAuthenticationToken 实例化了Authentication接口,继而按照流程,将其传递给AuthenticationMananger调用身份验证核心完成相关工作。
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
image.png
最后逻辑就很清晰了调的是UserDetailsService().loadUserByUsername方法,我们可以通过重写loadUserByUsername来对数据库和界面用户信息校验
2、主要代码分析
代码结构
image.png
SecurityConfig文件,通过重写WebSecurityConfigurerAdapter中的方法来自定义拦截授权配置,这里添加了
AuthenticationFailureHandler 和AuthenticationSuccessHandler 方便定位问题。
@Configuration
@EnableWebSecurity// 这个注解必须加,开启Security
@EnableGlobalMethodSecurity(prePostEnabled = true)//保证post之前的注解可以使用
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private AuthenticationFailureHandler failureHandler;
@Autowired
private AuthenticationSuccessHandler successHandler;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/login.html");
}
//拦截在这配,授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.successHandler(successHandler)
.failureHandler(failureHandler)
.loginPage("/index")
//进行登录校验
.loginProcessingUrl("/login").and()
.authorizeRequests()
.antMatchers( "/index").permitAll()
//匹配器登录成功跳转页面,这里加了角色校验
// .antMatchers("/home").hasAnyRole("st")
.anyRequest().authenticated() // 剩下所有的验证都需要验证
.and()
.csrf().disable().cors(); // 禁用 Spring Security 自带的跨域处理
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
重写的UserDetailsService方法,这里数据是自己模拟的。 实体类User 和SecurityUser自己补一下。
@Service
public class MyUserDetailsService implements UserDetailsService {
/***
* 根据账号获取用户信息
* @param :
* @return: org.springframework.security.core.userdetails.UserDetails
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//数据库信息
User user = new User();
user.setUsername("lili");
user.setPassword( new BCryptPasswordEncoder().encode("123456"));
if(!user.getUsername().equals(username) ) {
throw new UsernameNotFoundException("用户名或密码错误");
}
return new SecurityUser(user, getGrantedAuthority(user));
}
public List<GrantedAuthority> getGrantedAuthority(User user){
List<GrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return list;
}
}
前端页面 login.html,其中action这里是登录后要跳转的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h1>Spring Security</h1>
<form method="post" action="/test/login">
<div>
用户名:<input type="text" name="username" id="username">
</div>
<div>
密码:<input type="password" name="password" id="password">
</div>
<div>
<!-- <label><input type="checkbox" name="remember-me" id="remember-me"/>自动登录</label>-->
<button type="submit">登陆</button>
</div>
</form>
</body>
</html>
持续更新中。。。
三、添加durid数据源
pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/>
</parent>
<properties>
<mysql-connector-java.version>8.0.21</mysql-connector-java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--安全-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
配置文件,这里需要注意datasource下面没有druid这层,否则我这里出现了可以访问页面但无法识别filter的现象,就拦截不到sql
spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.filters= stat,wall,log4j
spring.datasource.filter.stat.enabled=true
spring.datasource.filter.stat.db-type=mysql
spring.datasource.filter.stat.log-slow-sql=true
spring.datasource.filter.stat.slow-sql-millis=2000
#是否启用StatFilter默认值true
spring.datasource.web-stat-filter.enabled=true
##spring.datasource.druid.web-stat-filter.url-pattern=
spring.datasource.web-stat-filter.exclusions=/static/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
# StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
#是否启用StatViewServlet默认值true
spring.datasource.stat-view-servlet.enabled=true
spring.datasource.stat-view-servlet.url-pattern=/druid/*
spring.datasource.stat-view-servlet.reset-enable=false
#初始化时建立物理连接的个数
spring.datasource.initial-size=5
最小连接池数量
spring.datasource.min-idle=5
spring.datasource.max-active=20
spring.datasource.max-wait=60000
spring.datasource.time-between-eviction-runs-millis=3000000
spring.datasource.validation-query=select 1 from dual
spring.datasource.test-on-borrow=false
spring.datasource.test-on-return=false
spring.datasource.test-while-idle=true
spring.datasource.pool-prepared-statements=true
spring.datasource.max-pool-prepared-statement-per-connection-size=20
spring.datasource.use-global-data-source-stat=true
spring.datasource.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
我这里用了springSecurity所以过滤的请求要将durid的请求拦截去掉,不然登录页面走的是springSecurity页面会出现登录无法跳转情况
image.png
请求链接localhost:port/druid/sql.html
image.png
参考链接
网友评论