1.SpringSecurity内置认证流程
通过研究SpringSecurity内置基于form表单认证的UsernamePasswordAuthenticationFilter过滤器,我们可以仿照自定义认证过滤器:
内置认证过滤器的核心流程:
核心流程梳理如下:
-
认证过滤器(UsernamePasswordAuthentionFilter)接收form表单提交的账户、密码信息,并封装成UsernamePasswordAuthenticationToken认证凭对象;
-
认证过滤器调用认证管理器AuthenticationManager进行认证处理;
-
认证管理器通过调用用户详情服务获取用户详情UserDetails;
-
认证管理器通过密码匹配器PasswordEncoder进行匹配,如果密码一致,则将用户相关的权限信息一并封装到Authentication认证对象中;
-
认证过滤器将Authentication认证过滤器放到认证上下文,方便请求从上下文获取认证信息;
2.自定义Security认证过滤器
UsernamePasswordAuthentionFilter过滤器继承了模板认证过滤器AbstractAuthenticationProcessingFilter抽象类,我们也可仿照实现:
仿照实现抽象类
package com.itheima.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Optional;
/**
* ClassName: MyAbstractAuthenticationProcessingFilter
* Package: com.itheima.security.filter
* Description:
*
* @Author R
* @Create 2024/2/12 12:08
* @Version 1.0
*/
public class MyAbstractAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
/**
* 自定义构造器 传入登录的地址
* @param loginUrl
*/
public MyAbstractAuthenticationProcessingFilter(String loginUrl) {
super(loginUrl);
}
/**
* 尝试认证
* @param request from which to extract parameters and perform the authentication
* @param response the response, which may be needed if the implementation has to do a
* redirect as part of a multi-stage authentication process (such as OpenID).
* @return
* @throws AuthenticationException
* @throws IOException
* @throws ServletException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
//不是applicationjson的格式
if (!request.getMethod().equalsIgnoreCase("POST") && !MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType())) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
HashMap<String,String> userInfo = new ObjectMapper().readValue(request.getInputStream(), HashMap.class);
//获取
String username = userInfo.get(SPRING_SECURITY_FORM_USERNAME_KEY);
username = (username != null) ? username : "";
username = username.trim();
String password = userInfo.get(SPRING_SECURITY_FORM_PASSWORD_KEY);
password = (password != null) ? password : "";
//组长认证票据对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
//交给认证管理器
return this.getAuthenticationManager().authenticate(authRequest);
}
//认证成功后执行
/**
*
* @param request
* @param response
* @param chain
* @param authResult the object returned from the <tt>attemptAuthentication</tt> 认证权限信息
* method.
* @throws IOException
* @throws ServletException、、
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
User principal = (User) authResult.getPrincipal();
String username = principal.getUsername();
Collection<GrantedAuthority> authorities = principal.getAuthorities();
//响应数据格式
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
//编码格式
response.setCharacterEncoding("UTF-8");
HashMap<String, String> info = new HashMap<>();
info.put("msg","登录成功");
info.put("data","");
info.put("code","1");
//响应
response.getWriter().write(new ObjectMapper().writeValueAsString(info));
}
//认证失败后
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
//响应数据格式
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
//编码格式
response.setCharacterEncoding("UTF-8");
HashMap<String, String> info = new HashMap<>();
info.put("msg","认证失败");
info.put("data","");
info.put("code","0");
//响应
response.getWriter().write(new ObjectMapper().writeValueAsString(info));
}
}
定义获取用户详情服务bea
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private TbUserMapper tbUserMapper;
//根据认证时传入的用户名去指定数据库下获取用户相关的详情信息 用户名 密文密码 权限集合
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
TbUser user = tbUserMapper.findByUserName(username);
if (user==null) {
throw new UsernameNotFoundException("用户不存在");
}
//构建认证明细对象
//组装用户详细信息
List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles());
UserDetails build = User.builder().username(user.getUsername())
.password(user.getPassword())
.authorities(list).build();
return build;
}
}
定义SecurityConfig类
配置默认认证过滤器,保证自定义的认证过滤器要在默认的认证过滤器之前;
@Configuration
@EnableWebSecurity//开启web安全设置生效
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//定义认证时使用form表单的方式提交数据
.and()
.logout()//登出用默认的路径登出 /logout
.permitAll()//允许所有的用户访问登录或者登出的路径,如果 .anyRequest().authenticated()注释掉,则必须添加permitAll(),否则就不能正常访问登录或者登出的路径
.and()
.csrf().disable()
.authorizeRequests();//授权方法,该方法后有若干子方法进行不同的授权规则处理
//将自定义的过滤器加入到security过滤器链,且在默认的认证过滤器之前执行
http.addFilterBefore(myAbstractAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
}
// @Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
// @Bean
// public BCryptPasswordEncoder bCryptPasswordEncoder(){
// return new BCryptPasswordEncoder();
// }
@Bean
public PasswordEncoder passwordEncoder() {
log.info("000000000000000000000000000000000000000000000加载");
return new BCryptPasswordEncoder();
}
/**
* 自定义过滤器 认证成功 上下文中维护认证相关的信息
* 如果上下文中存在 泽默认的UsernamePasswordAuthentionFilter就不在认证 我们的在前
* @return
* @throws Exception
*/
@Bean
public MyAbstractAuthenticationProcessingFilter myAbstractAuthenticationProcessingFilter() throws Exception {
//构造认证过滤器对象 设置认证路径
MyAbstractAuthenticationProcessingFilter myAbstractAuthenticationProcessingFilter = new MyAbstractAuthenticationProcessingFilter("/myLogin");
myAbstractAuthenticationProcessingFilter.setAuthenticationManager(authenticationManagerBean());
return myAbstractAuthenticationProcessingFilter;
}
}
接口测试
tips:当查询数据库的数据时 要注意存储的密文加前缀
3.执行流程
因为默认实现的抽象类执行的是表单提交 cookie和session的形式 而对于前后端分离并不适用,因此自定义继承抽象类,以json的形式接受数据 响应数据
组装成凭据对象交给认证管理器
根据认证时传入的用户名去指定数据库下获取用户相关的详情信息 用户名 密文密码 权限集合,构建认证明细对象 组装用户详细信息
spring底层自动执行match方法
执行成功 回调方法