项目地址https://github.com/luozijing/springboot-/tree/dev
目录
项目地址https://github.com/luozijing/springboot-/tree/dev
登录页面
登录成功,角色为role1,可以设计不同的角色,能够显示不同的角色信息。
springSecurity执行原理
security流程主要由过滤器链组成,其中比较要的过滤器由AbstractAuthenticationProcessingFilter ,它的作用是帮助我们验证用户权限和密码的正确。
验证流程
https://blog.csdn.net/weixin_34161064/article/details/93164080
UsernamePasswordAuthenticationFilter是其的子类,用户验证流程如下所示。
进入 ProviderManager 类后会调用 authenticate(Authentication authentication) 方法,它通过 AuthenticationProvider 实现类获取用户的登录的方式,然后会有一个 while 迭代器模式的循环遍历,检查它是否支持这种登录方式,具体的登录方式有表单登录,qq登录,微信登录等。如果最终都不支持会抛出相应的异常信息,如果支持则会进入AuthenticationProvider 接口的抽象实现类 AbstractUserDetailsAuthenticationProvider 中。
进入 AbstractUserDetailsAuthenticationProvider 类后会调用 authenticate(Authentication authentication) 方法对用户的身份进行校验,首先是判断用户是否为空,这个 user 是 UserDetail 的对象,如果为空,表示还没有认证,就需要调用 retrieveUser 方法去获取用户的信息,这个方法是抽象类 AbstractUserDetailsAuthenticationProvider 的扩展类DaoAuthenticationProvider 的一个方法。
在该扩展类的 retrieveUser 方法中调用 UserDetailsService 这个接口的实现类的 loadUserByUsername 方法去获取用户信息,而这里我自己编写了实现类 UserDetailsServiceImpl 类,在这个实现类中,我们可以编写自己的逻辑,从数据库中获取用户密码等权限信息返回。
本地 UserDetailService 实现类 UserDetailsServiceImpl(我们经常需要用到的实现类)
此时 authorities 不再为空了。
在 UsernamePasswordAuthenticationToken 的父类中,它会检查用的权限,如果有一个为 null,表示权限没有相应的权限,抛出异常。
然后在 createSuccessAuthentication 方法返回后回到 ProvioderManager 的 authenticate 方法中返回 result,最后回到UsernamePasswordAuthenticationFilter 的刚开始进入的 attemptAuthentication 方法中返回。
attemptAuthentication() 方法中的返回,返回到哪?
再回到第一张图,UsernamePasswordAuthenticationFilter 父类 doFilter() 方法,返回值就是 authResult,如果过程中发现存在异常则执行 unsuccessfulAuthentication.onAuthenticationFailure() 方法,如果认证成功则执行 successfulAuthentication.onAuthenticationSuccess() 方法,再结合上边提到的自定义 成功/失败处理类。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
protected ApplicationEventPublisher eventPublisher;
protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationManager authenticationManager;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private RememberMeServices rememberMeServices = new NullRememberMeServices();
private RequestMatcher requiresAuthenticationRequestMatcher;
private boolean continueChainBeforeSuccessfulAuthentication = false;
private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
private boolean allowSessionCreation = true;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
this.setFilterProcessesUrl(defaultFilterProcessesUrl);
}
protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
}
public void afterPropertiesSet() {
Assert.notNull(this.authenticationManager, "authenticationManager must be specified");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
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) {
//如果抛出这两个异常,则表明登陆失败。进入这个方法 unsuccessfulAuthentication()
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);
}
//登陆成功之后,调用此方法 successfulAuthentication()
this.successfulAuthentication(request, response, chain, authResult);
}
}
}
认证之后的成功(失败)处理 来到最开始的AbstractAuthenticationProcessingFilter的doFilter中
如果期间没发成异常,则认证成功,调用successfulAuthentication()方法,进而调用SuccessHandler.onAuthenticationSuccess()方法。
具体源码可以流程可以参考这个
springSecurity配置
package com.students.springbootstu.security.config;
import com.students.springbootstu.configuration.SwaggerConfig;
import com.students.springbootstu.security.handler.MyAuthenctiationFailureHandler;
import com.students.springbootstu.security.handler.MyAuthenticationSuccessHandler;
import com.students.springbootstu.security.handler.MyLoginUrlAuthenticationEntryPoint;
import com.students.springbootstu.security.handler.MyLogoutSuccessHandler;
import com.students.springbootstu.security.service.MyUserDetailService;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
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;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* web相关安全配置
*/
@Autowired
//配置属性类
private SecurityProperties securityProperties;
@Autowired
//用户账号权限验证类
private MyUserDetailService myUserDetailService;
@Autowired
//注销成功处理器
private MyLogoutSuccessHandler myLogoutSuccessHandler;
@Autowired
//成功验证处理器
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Value("${swagger.enable}")
private boolean swaggerEnable;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
//loginPage("/testlogin") 相当于一个普通请求。这里则需要发送到登陆页面
//loginProcessingUrl("/login") 登陆页面表单提交的action.必须为post请求
//defaultSuccessUrl("/index") 登陆成功后,发送这个请求
http
.authorizeRequests()
//设置匿名访问的url
.antMatchers(securityProperties.getAnonymous()).permitAll()
//所有请求都要验证
.anyRequest().authenticated()
.and()
.formLogin()
//自定义登录页
.loginPage(securityProperties.getLoginPage())
.successHandler(myAuthenticationSuccessHandler)
//用户验证失败处理
.failureHandler(new MyAuthenctiationFailureHandler(securityProperties.getLoginPage()))
//自定义登录请求 登录页面表单提交的action.必须为post请求
.loginProcessingUrl(securityProperties.getLoginProcessingUrl())
.permitAll()
.and()
.logout()
.logoutSuccessHandler(myLogoutSuccessHandler)
.permitAll()
.and()
.exceptionHandling()
//自定义认证失败处理器
.authenticationEntryPoint(new MyLoginUrlAuthenticationEntryPoint(securityProperties.getLoginPage()));
}
@Override
public void configure(WebSecurity web) throws Exception{
String[] ignoringArray = securityProperties.getIgnoringArray();
if(swaggerEnable && !StringUtils.isBlank(SwaggerConfig.ACCESS_PREFIX)){
ignoringArray = ArrayUtils.addAll(ignoringArray,SwaggerConfig.ACCESS_PREFIX.split(","));
}
web.ignoring().antMatchers(ignoringArray);
}
/**
* 声明密码加密方式
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());
// 配置密码加密方式,也可以不指定,默认就是BCryptPasswordEncoder
}
}
验证成功处理器
https://www.cnblogs.com/deviltofree/p/10044046.html(登录失败异常处理)
package com.students.springbootstu.security.handler;
import com.students.springbootstu.common.ResponseResult;
import com.students.springbootstu.util.RequestUtil;
import com.students.springbootstu.util.ResponseUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
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 MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
/**
* 登录成功处理器
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
// 判断是否是ajax请求,因为ajax请求不能跳转页面,所以要单独判断
if (RequestUtil.isAjaxRequest(request)) {
ResponseUtil.printJson(response, ResponseResult.success());
} else {
response.sendRedirect("/");
}
}
}
用户账号权限验证类
package com.students.springbootstu.security.service;
import com.students.springbootstu.entity.TbStudent;
import com.students.springbootstu.entity.TbStupwd;
import com.students.springbootstu.service.TbStudentService;
import com.students.springbootstu.service.TbStupwdService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Service
public class MyUserDetailService implements UserDetailsService {
@Resource
private TbStupwdService tbStupwdService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//username要对应html里面的表单提交name
TbStupwd user = tbStupwdService.queryByStuAccount(username);
if(user == null){
throw new UsernameNotFoundException("userName not found");
}
//创建一个集合来放置权限
Collection<GrantedAuthority> authorities = getAuthorities(user);
//实例化一个userDetails对象
UserDetails userDetails = new User(username, user.getStupwd(), true,true,true,true, authorities);
return userDetails;
}
private Collection<GrantedAuthority> getAuthorities(TbStupwd user){
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
//注意:这里每个权限前面都要加ROLE_。否在最后验证不会通过
authList.add(new SimpleGrantedAuthority("ROLE_"+user.getRole()));
return authList;
}
}
thymleaf+bootstrap模板
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>login</title>
<meta content="text/html;charset=UTF-8"/>
<link th:href="@{bootstrap/css/bootstrap.css}" rel="stylesheet" >
<link th:href="@{css/login.css}" rel="stylesheet">
<script th:src="@{scripts/jQuery3.15min.js}" type="text/javascript"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script th:src="@{bootstrap/js/bootstrap.js}" type="text/javascript"></script>
</head>
<body>
<!--nav-->
<div th:replace="common::topbar"></div>
<!-- body -->
<div class="container">
<div class="text-center">
<form class="form-signin" th:action="@{/login}" method="post">
<img class="mb-4" alt="" width="72" height="72" th:src="@{images/bootstrap-solid.svg}">
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="username" class="sr-only" >Email address</label>
<input id="username" class="form-control" name="username" placeholder="account" required autofocus>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" class="form-control" placeholder="Password" name="password" required>
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" >Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2020</p>
</form>
</div>
</div>
<!--footer-->
<div th:replace="common::bottombar"></div>
</body>
</html>
其他配置(session)
https://www.jianshu.com/p/caa1d35d3524