SpringSecurity整合JWT
1.认证思路分析
SpringSecurity主要是通过过滤器来实现功能的!我们要找到SpringSecurity实现认证和校验身份的过滤器!
回顾集中式认证流程
用户认证: 使用UsernamePasswordAuthenticationFilter 过滤器中attemptAuthentication方法实现认证功能,该过滤器父类中successfulAuthentication方法实现认证成功后的操作。
身份校验: 使用 BasicAuthenticationFilter 过滤器中doFilterInternal方法验证是否登录,以决定能否进入后续过滤器。
2.分析分布式认证流程
用户认证:
由于是前后端分离,项目架构设计是分布式的,我们要满足可以接受异步post的认证请求参数,需要修改UsernamePasswordAuthenticationFilter过滤器中attemptAuthentication方法,让其能够接收请求体。 另外,默认successfulAuthentication方法在认证通过后,是把用户信息直接放入session就完事了,现在我们需要修改这个方法,在认证通过后生成token并返回给用户。
身份校验
BasicAuthenticationFilter
使用BasicAuthenticationFilter 过滤器中 doFilterInternal方法 校验用户是否登录,就是看session中是否有用户信息,我们要修改为,验证用户携带的token是否合法,并解析出用户信息,交给SpringSecurity,以便于后续的授权功能可以正常使用。
权限框架SpringSecurity 前后端分离登录 (BasicAuthenticationFilter)
https://blog.csdn.net/zxd1435513775/article/details/124801891
// 自定义AuthenticationManager
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
}
认证管理器,负责验证,认证成功后,AuthenticationManager 返回一个填充了用户认证信息(包括权限信息、身份信息、详细信息等,但密码通常会被移除)的 Authentication 实例。
然后再将 Authentication 设置到 SecurityContextHolder
容器中。
AuthenticationManager 接口是认证相关核心接口,也是发起认证的入口。但它一般不直接认证,其常用实现类 ProviderManager 内部会维护一个List<AuthenticationProvider>
列表,存放多种认证方式,默认情况下只需要通过一个 AuthenticationProvider 的认证,就可以认为是登录成功。
UserDetailsService
loadUserByUsername
加载登录账户 loadUserByUsername
https://blog.csdn.net/u012811805/article/details/118401750
AuthenticationManager 的 authentication 过程
链接:https://blog.csdn.net/wwang_dev/article/details/119107355
// 执行登录认证过程
Authentication authentication = authenticationManager.authenticate(token);
// 认证成功存储认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成令牌并返回给客户端
String genToken = JwtTokenUtils.generateToken(authentication);
token.setToken(genToken);
// 保存权限到redis
redisTemplate.opsForValue().set(genToken, JSONArray.toJSONString(authentication.getAuthorities()));
return token;
从这里也可以看出为什么 pinciple 和 credentials 的类型都是 Object,因为 pinciple 认证前需要传 String 类型的 username,认证后需要传实现了UserDetails 接口的用户对象;而 credentials 认证前需要传前端传来的String类型的password,认证后需要传 String类型的密码 或者 null。
通常都是,接收 前端传来的 username 和 password,调用 两个参数的构造器,创建一个未认证的对象,交给AuthenticationManager进行认证。认证成功后,调用三个参数的构造器,创建一个已认证的对象。
@Order(10)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig2 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
//认证服务器 配置用户认证 // 使用自定义身份验证组件 //指定密码加密策略
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(userDetailsService);
auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
}
}
9 自定义获取数据库用户权限实现
改造 WebSecurityConfiguration 加载自定义UserDetailsService
9.1 UserDetailsService实现
根据用户名获取用户的权限,链接:https://blog.csdn.net/tiancxz/article/details/108856337
//返回一个自定义数据库获取用户权限的实现
@Autowired
public UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
每次获取token时,都会进入自定义实现从数据库获取权限
http://client:secret@localhost:10101/oauth/token
There is no PasswordEncoder mapped for the id “null”
DaoAuthenticationProvider 身份验证提供者
史上最简单的Spring Security教程(二十六):DaoAuthenticationProvider详解
https://blog.csdn.net/liuminglei1987/article/details/108279836
说到 Authentication 相信都不陌生。最著名的,便是 UsernamePasswordAuthenticationToken。
而 DaoAuthenticationProvider 是用于解析并认证 UsernamePasswordAuthenticationToken 的这样一个认证服务提供者。
其最终目的,就是根据 UsernamePasswordAuthenticationToken,获取到 username,然后调用 UserDetailsService 检索用户详细信息。
在其基类 AbstractUserDetailsAuthenticationProvider 中,需要子类实现 retrieveUser() 、additionalAuthenticationChecks() 等抽象方法。
A系统(单点登录客户端)首次访问受保护的资源触发单点登录流程说明
1、用户通过浏览器访问A系统被保护的资源链接
2、A系统判断当前会话是否登录,如果没有登录则跳转到A系统登录地址/login
3、A系统首次接收到/login请求时没有state和code参数,此时A系统拼接系统配置的单点登录服务器授权url,并重定向至授权链接。
4、单点登录服务器判断此会话是否登录,如果没有登录,那么返回单点登录服务器的登录页面。
5、用户在登录页面填写用户名、密码等信息执行登录操作。
6、单点登录服务器校验用户名、密码并将登录信息设置到上下文会话中。
7、单点登录服务器重定向到A系统的/login链接,此时链接带有code和state参数。
8、A系统再次接收到/login请求,此请求携带state和code参数,系统A通过OAuth2RestTemplate请求单点登录服务端/oauth/token接口获取token。
9、A系统获取到token后,首先会对token进行解析,并使用配置的公钥对token进行校验(非对称加密),如果校验通过,则将token设置到上下文,下次访问请求时直接从上下文中获取。
10、A系统处理完上下问会话之后重定向到登录前请求的受保护资源链接。
B系统(单点登录客户端)访问受保护的资源流程说明
1、用户通过浏览器访问B系统被保护的资源链接
2、B系统判断当前会话是否登录,如果没有登录则跳转到B系统登录地址/login
3、B系统首次接收到/login请求时没有state和code参数,此时B系统拼接系统配置的单点登录服务器授权url,并重定向至授权链接。
4、单点登录服务器判断此会话是否登录,因上面访问A系统时登陆过,所以此时不会再返回登录界面。
5、单点登录服务器重定向到B系统的/login链接,此时链接带有code和state参数。
6、B系统再次接收到/login请求,此请求携带state和code参数,系统B通过OAuth2RestTemplate请求单点登录服务端/oauth/token接口获取token。
7、B系统获取到token后,首先会对token进行解析,并使用配置的公钥对token进行校验(非对称加密),如果校验通过,则将token设置到上下文,下次访问请求时直接从上下文中获取。
8、B系统处理完上下问会话之后重定向到登录前请求的受保护资源链接。
http.formLogin()
loginProcessingUrl
loginProcessingUrl(“/login/doLogin”)//表单提交路劲,提交后执行认证逻辑
loginProcessingUrl的作用是用来拦截前端页面对/login/doLogin这个的请求的,拦截到了就走它自己的处理流程,导致我们自己后端写的/login/doLogin这个接口有写跟没写是一样的。
successHandler
**(重点)**由于未在WebSecurity配置中设置successHandler,因此默认情况下将调用SavedRequestAwareAuthenticationSuccessHandler。
ExceptionTranslationFilter在认证开始前把request缓存到session中,当认证成功后,在SavedRequestAwareAuthenticationSuccessHandler里取出缓存的request,跳转回认证前用户想访问的url
AuthenticationSuccessHandler接口(登录成功之后的处理逻辑)
异常处理
SpringSecurity(二十):异常处理 https://www.cnblogs.com/wangstudyblog/p/14823512.html
http.exceptionHandling().authenticationEntryPoint()
http.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationExceptionEntryPoint())
Spring Security 执行登录认证过程
图 https://blog.csdn.net/weixin_64742764/article/details/124813433
参考地址
Spring Security + JWT 实现单点登录
https://blog.csdn.net/xiyang_1990/article/details/124487755
https://blog.csdn.net/CSDN_KONGlX/article/details/125486825
https://blog.csdn.net/qq_18671415/article/details/118518450
http://t.zoukankan.com/7788IT-p-10693154.html
https://blog.csdn.net/pxg943055021/article/details/124752669
https://blog.csdn.net/wang121213145/article/details/124850518
https://blog.csdn.net/asd747571569/article/details/122511434
OAuth 2.0实现分布式认证授权-认证服务器配置 2
https://blog.csdn.net/u011066470/article/details/119974904
Spring Security
https://blog.csdn.net/m0_64714024/article/details/125549592
OAuth2.0认证和授权原理(示例代码)
https://www.136.la/tech/show-45950.html
Spring Security Oauth2 常用授权方式配置详细教程(一)
https://blog.csdn.net/tiancxz/article/details/108856337
史上最简单的Spring Security教程(二十六):DaoAuthenticationProvider详解
https://blog.csdn.net/liuminglei1987/article/details/108279836
认证成功后回跳
Spring Security认证成功后回跳
https://blog.csdn.net/gangsijay888/article/details/81171647/
SpringSecurity保存登录前的请求页面和跳转回登录前页面的源码分析
https://blog.csdn.net/HHoao/article/details/124559410
SpringSecurity登录后重定向到登录前访问页面策略 https://blog.csdn.net/ainizfb/article/details/123221490
基于SpringSecurity OAuth2实现单点登录——集成Github实现第三方账户登录
https://blog.csdn.net/hou_ge/article/details/120455557
其他
There is no PasswordEncoder mapped for the id "null"的解决办法
https://www.cnblogs.com/jpfss/p/11004828.html
ajax
https://wenku.baidu.com/view/917ac6e0a900b52acfc789eb172ded630b1c988c.html
springsecurity oauth2使用jwt实现单点登录(文章目录)
https://blog.csdn.net/weixin_44837750/article/details/125767591
自定义权限路径配置
https://blog.csdn.net/weixin_50025568/article/details/123548098
测试页 备份
@Order(10)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig3 extends WebSecurityConfigurerAdapter {
@Override //SuperAdmin Admin
public void configure(AuthenticationManagerBuilder auth) throws Exception{
// 使用自定义身份验证组件,指定密码加密策略
//auth.inMemoryAuthentication().withUser("admin").password("123456").roles("Admin").and().passwordEncoder(passwordEncoder());
//auth.userDetailsService(userDetailsService);
auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123456")).roles("Admin");
}
@Bean
public static PasswordEncoder passwordEncoder(){
// return NoOpPasswordEncoder.getInstance();
return new BCryptPasswordEncoder();
}
//form表单重写
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//.loginPage("/login.html")
//.defaultSuccessUrl("/index_defaultSuccessUrl.html")
//.loginProcessingUrl("/login")//表单提交路劲,提交后执行认证逻辑
//.successForwardUrl("/index.html")
//.successHandler((request, response, auth) -> { System.out.println(" doLogin"); })
//
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
RequestCache cache = new HttpSessionRequestCache();
SavedRequest savedRequest = cache.getRequest(request, response);
String url = savedRequest.getRedirectUrl();
System.out.println("getRedirectUrl: " + url);
response.sendRedirect(url);
}
})
.failureForwardUrl("/fail.html"); // 登录失败后的路劲
//
http.authorizeRequests()
.antMatchers("/sso/oauth/*").permitAll()//
.antMatchers("/login.html").permitAll()//登录页不需要认证
.antMatchers("/login").permitAll()
.antMatchers("/**/*.js", "/**/*.css").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}