前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >spring security 深度使用

spring security 深度使用

作者头像
六个核弹
发布于 2022-12-23 12:53:52
发布于 2022-12-23 12:53:52
58600
代码可运行
举报
运行总次数:0
代码可运行
从一个基础的springsecurity开始,进行代码跟踪分析其原理

springsecurity是一个典型的责任链模式;我们先新建一个springboot项目,进行最基本的springsecurity配置,然后debug;我这里使用的开发工具是idea.建议大家也使用idea来进行日常开发。好了话不多说,开始:

第一步

新建springboot项目 maven依赖:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</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.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency></dependencies>

启动项目,控制台上会输出这样一段字符串:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2019-04-11 09:47:40.388  INFO 16716 --- [           main] .s.s.UserDetailsServiceAutoConfiguration :

Using generated security password: a6d55bc6-49fb-4241-a5ae-527e5e644731

现在我们访问 http://localhost:8080 会自动跳转到 http://localhost:8080/login,并弹出一个登陆页面,用户名输user,密码输上面的字符串: a6d55bc6-49fb-4241-a5ae-527e5e644731,每次字符串都是随机的,要留意你的控制台打印的字符串,登陆成功了,现在我们开始debug,看看一次登陆和一次登陆后访问一次不登陆访问这三种情况security都做了哪些事情。

先写一个接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestControllerpublic class TestController {@GetMapping("test")public String test(){return "hi 你好啊";
}
}

springsecurity 执行过程是走一条过滤器链,所以我们要先明白,有哪些过滤器,并在过滤器上打上断点追踪,下面贴出各个过滤器名称及作用:

1、WebAsyncManagerIntegrationFilter 将Security上下文与Spring Web中用于处理异步请求映射的 WebAsyncManager 进行集成。 2、SecurityContextPersistenceFilter 在每次请求处理之前将该请求相关的安全上下文信息加载到SecurityContextHolder中,然后在该次请求处理完成之后,将SecurityContextHolder中关于这次请求的信息存储到一个“仓储”中,然后将SecurityContextHolder中的信息清除 例如在Session中维护一个用户的安全信息就是这个过滤器处理的。 3、HeaderWriterFilter 用于将头信息加入响应中 4、CsrfFilter 用于处理跨站请求伪造 5、LogoutFilter 用于处理退出登录 6、UsernamePasswordAuthenticationFilter 用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自“/login”的请求。 从表单中获取用户名和密码时,默认使用的表单name值为“username”和“password”,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。 7、DefaultLoginPageGeneratingFilter 如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。 8、BasicAuthenticationFilter 处理请求头信息,DigestAuthenticationFilter 9、RequestCacheAwareFilter 用来处理请求的缓存 10、SecurityContextHolderAwareRequestFilter 11、AnonymousAuthenticationFilter 12、SessionManagementFilter 13、ExceptionTranslationFilter 处理 AccessDeniedException 和 AuthenticationException 异常 14、FilterSecurityInterceptor AbstractInterceptUrlConfigurer.createFilterSecurityInterceptor 15、RememberMeAuthenticationFilter的作用是, 当用户没有登录而直接访问资源时, 从cookie里找出用户的信息, 如果Spring Security能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统.

先不管那么多,ctr+alt+shift+t(或者按两下shift) 输入类名找到类,打断点,开始debug

springsecurity 的默认登陆url是/login:我们访问一下来捋一捋:

其代码顺序是: WebAsyncManagerIntegrationFilter -> SecurityContextPersistenceFilter -> HeaderWriterFilter -> CsrfFilter -> LogoutFilter -> DefaultLoginPageGeneratingFilter

然后浏览器会返回一个登陆页面:

说明:

这其实是发起了一个请求登陆页面的请求,请求首先进入WebAsyncManagerIntegrationFilter 这个过滤器做了什么我们不必去关系,它下一个过滤器SecurityContextPersistenceFilter 通过request的attribute来上了一次请求锁,并且对SecurityContextHolder进行了管理;而SecurityContextHolder是对用户信息进行管理的一套系统,分为三大类

  1. SecurityContextHolder 对用户信息增删改查的操作
  2. SecurityContext 将用户名,权限等封装成该对象
  3. SecurityContextHolderStrategy 用于存储 SecurityContext

默认的SecurityContextHolderStrategy 实现类是ThreadLocalSecurityContextHolderStrategy 它使用了ThreadLocal来存储了用户信息。 下一个过滤器HeaderWriterFilter请求和响应封装了一下,用于CsrfFilter鉴别csrf攻击 LogoutFilter判断是不是登出操作,如果是则不执行下面的过滤器,而执行登出的相关操作,DefaultLoginPageGeneratingFilter生成登录页

我们先输入正确的代码登陆一次

通过跟踪代码发现其执行顺序是: WebAsyncManagerIntegrationFilter -> SecurityContextPersistenceFilter -> HeaderWriterFilter->CsrfFilter-> LogoutFilter->UsernamePasswordAuthenticationFilter->WebAsyncManagerIntegrationFilter->SecurityContextPersistenceFilter->HeaderWriterFilter->CsrfFilter->LogoutFilter->DefaultLoginPageGeneratingFilter->BasicAuthenticationFilter->RequestCacheAwareFilter->SecurityContextHolderAwareRequestFilter->AnonymousAuthenticationFilter->SessionManagementFilter->ExceptionTranslationFilter->FilterSecurityInterceptor->SecurityContextPersistenceFilter->LogoutFilter->DefaultLoginPageGeneratingFilter->RequestCacheAwareFilter->SecurityContextHolderAwareRequestFilter->AnonymousAuthenticationFilter->SessionManagementFilter->ExceptionTranslationFilter->FilterSecurityInterceptor

这一次之所以过滤器链跑这么长是因为,这里可以认为发起了三次请求,第一次请求是登陆,登陆成功后转发到 url 为“/”的接口,而我没有这个接口,发生了重定向到“/error” 而“/error”页面是springboot请求失败的错误页面返回机制。 WebAsyncManagerIntegrationFilter -> SecurityContextPersistenceFilter -> HeaderWriterFilter->CsrfFilter-> LogoutFilter->UsernamePasswordAuthenticationFilter 这一过程登陆请求。

WebAsyncManagerIntegrationFilter->SecurityContextPersistenceFilter->HeaderWriterFilter->CsrfFilter->LogoutFilter->DefaultLoginPageGeneratingFilter->BasicAuthenticationFilter->RequestCacheAwareFilter->SecurityContextHolderAwareRequestFilter->AnonymousAuthenticationFilter->SessionManagementFilter->ExceptionTranslationFilter->FilterSecurityInterceptor 这是转发到“/”的过程

SecurityContextPersistenceFilter->LogoutFilter->DefaultLoginPageGeneratingFilter->RequestCacheAwareFilter->SecurityContextHolderAwareRequestFilter->AnonymousAuthenticationFilter->SessionManagementFilter->ExceptionTranslationFilter->FilterSecurityInterceptor 这是重定向到error的过程

接下来访问一下我们的/test接口 看看执行顺序: WebAsyncManagerIntegrationFilter->SecurityContextPersistenceFilter->HeaderWriterFilter->CsrfFilter->LogoutFilter->DefaultLoginPageGeneratingFilter->BasicAuthenticationFilter->RequestCacheAwareFilter->SecurityContextHolderAwareRequestFilter->AnonymousAuthenticationFilter->SessionManagementFilter->ExceptionTranslationFilter->FilterSecurityInterceptor

说明:

BasicAuthenticationFilter就是看你请求头里面有没有basic开头的东西,有的话做一些处理,对我们来说没啥用,不必去关心,RequestCacheAwareFilter对请求和响应做了额外处理 SecurityContextHolderAwareRequestFilter 也是对请求做了一些额外处理,我们同样不去关心它。AnonymousAuthenticationFilter过滤器是当securitycontext为null时填充一个匿名权限,这里被执行的原因因为security未配置完全,后面进一步配置了之后再回来详解。ExceptionTranslationFilter是对鉴权或者登陆异常的处理过滤器,FilterSecurityInterceptor可以看做是过滤器链的出口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);this.invoke(fi);
}

这个fi就是请求的url值。

现在我们完善一下security配置,让它复杂一点 实现 GrantedAuthority:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Datapublic class MyGrantedAuthority implements GrantedAuthority {private String authority;
}

实现UserDetails

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Data@Accessors(chain = true)public class MyUserDetail implements UserDetails {private List<MyGrantedAuthority> authorities;private String password;private String username;private boolean accountNonExpired;private boolean accountNonLocked;private boolean credentialsNonExpired;private boolean enabled;

}

实现UserDetailsService

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Servicepublic class MyUserDetailService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
List<MyGrantedAuthority> authorities = new ArrayList<>();
MyGrantedAuthority myGrantedAuthority = new MyGrantedAuthority();
myGrantedAuthority.setAuthority("ROLE_test");
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String test = bCryptPasswordEncoder.encode("test");
authorities.add(myGrantedAuthority);return new MyUserDetail().setAuthorities(authorities).setAccountNonExpired(true).setAccountNonLocked(true).setCredentialsNonExpired(true).setEnabled(true).setPassword(test).setUsername("test");
}
}

重写security适配器WebSecurityConfigurerAdapter:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredMyUserDetailService userDetailService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());
}@Overridepublic void configure(WebSecurity web) throws Exception {

web.ignoring().antMatchers("/resources/**/*.html", "/resources/**/*.js","/resources/**/*.css", "/resources/**/*.txt","/resources/**/*.png", "/**/*.bmp", "/**/*.gif", "/**/*.png", "/**/*.jpg", "/**/*.ico");//        super.configure(web);}@Overrideprotected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login_page").passwordParameter("username").passwordParameter("password").loginProcessingUrl("/sign_in").permitAll()
.and().authorizeRequests().antMatchers("/test").hasRole("test").anyRequest().authenticated().and().csrf().disable();
}

}

这里比开始又要复杂一点了,我先对相关操作进行说明一下: 先从SecurityConfig说起

重写了三个config方法 1.第一个config涉及到的问题比较深——security的认证鉴权系统; 先说认证的过程,当过滤器跑到usernamepasswordFilter的时候就开始做认证了

security认证原理

认证的工作是交给AuthenticationManager去做,AuthenticationManager下有多个认证器 AuthenticationProvider 只要其中一个AuthenticationProvider通过认证就算登陆成功,而且在认证器中抛出异常,无法终止认证流程只是算该认证器未通过。 第一个config就算配置了一个AuthenticationManagerBuilder 这个类会生成一个 AuthenticationManager和DaoAuthenticationProvider认证器,认证调用userdetailservice 的loadUserByUsername方法来和你传入的username passworde做比较,password 是通过BCryptPasswordEncoder来做编码后比较的,这样做是为了提高安全性。

2.第二个config是对静态资源的放行;

3.第三个config 配置了登录页请求路径,登陆认证路径,用户名密码属性,和一个test权限,注意一点:我在config配的是hasRole(“test”),我设置的权限是 myGrantedAuthority.setAuthority(“ROLE_test”);为什么这样弄后面会说。 接下来完善一下,边边角角,写个登陆的HTML,一个登陆页面请求接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form method="post" action="/sign_in">用户名:<input type="text" name="username" value="test"><br>密码:<input type="text" name="password" value="test"><br><input type="text" name="verification"><br><input type="submit" name="test"></form></body></html>
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Controllerpublic class LoginController {@GetMapping("/login_page")public String loginPage(){return "loginPage.html";
}
}

启动项目,访问localhost:8080/test 跳转到了/login_page

点登陆,debug看看有什么不同 新增的断点DaoAuthenticationProvider和MyUserDetailService 前面执行顺序还是一样,到SecurityContextHolderAwareRequestFilter的时候我们注意一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {private String rolePrefix = "ROLE_";

我们权限设置的前缀就是从这来的 到后面讲投票器的时候再细说。 到UsernamePasswordAuthenticationFilter这个过滤器后 下一步到了DaoAuthenticationProvider验证器,验证器执行的是其父类AbstractUserDetailsAuthenticationProvider的authenticate()方法同时我们看到MyUserDetailService的loadUserByUsername被调用,最后,认证结束转发到访问前路径/test 重新走过滤器。流程和一开始的简单配置一样,走到FilterSecurityInterceptor的时候进入WebExpressionVoter了投票器;

security的投票器:

当过滤器链走到尽头(FilterSecurityInterceptor)下一步就是鉴权了,鉴权功能会交给AccessDecisionManager去处理,而AccessDecisionManager下又有多个投票器,其中WebExpressionVoter是security的一个默认投票器,我们来分析一下这个类: 其有个这样的方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes)

返回int类型,1表示赞成,0表示弃权,-1表示反对。当所有投票器的vote执行结束,如果最终结果小于0表示不通过, 方法的参数说明:

  1. FilterInvocation 可获得请求的相关信息,比如请求方式(get post)url 等 2.authentication 是从securitycontext中拿出来的用户信息 3.Collection 是可以访问该路径的权限集合。也就是前面SecurityContextHolderAwareRequestFilter 查找出来的权限,对于在WebSecurityConfigurerAdapter config方法中的hasRole(“test”)权限规则并不是直接取权限 test,而是加了前缀rolePrefix(“ROLE_”),这个前缀也是可配置的配置方式:https://docs.spring.io/spring-security/site/docs/5.0.6.RELEASE/reference/htmlsingle/#appendix-faq-role-prefix

一些基本的知识点交代的差不多了,现在,我进行下一步

security魔改

需求:我希望后端能做验证码校验,没通过校验的直接登录失败; 实现方式:security给我们提供了在各个过滤器上追加过滤器的方法,我们在UsernamePasswordAuthenticationFilter追加一个过滤器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyUsernamePasswordAuthenticationFilte extends UsernamePasswordAuthenticationFilter {private RedisService redisService;private boolean postOnly = true;public MyUsernamePasswordAuthenticationFilte(RedisService redisService){this.redisService=redisService;
}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
redisService.getcCode(request);return super.attemptAuthentication(request,response);
}
}

这里redisService 就是你要弄的验证逻辑,其他代码还是从父类那复制过来,不去动它。 为什么要继承UsernamePasswordAuthenticationFilter 而不是继承AbstractAuthenticationProcessingFilter,这样做的好处是可以少写代码少踩坑。 然后修改config

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@AutowiredRedisService redisService;@Overrideprotected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login_page").passwordParameter("username").passwordParameter("password").loginProcessingUrl("/sign_in").permitAll()
.and().authorizeRequests().antMatchers("/test").hasRole("test").anyRequest().authenticated().and().csrf().disable();
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
}MyUsernamePasswordAuthenticationFilte getAuthenticationFilter(){
MyUsernamePasswordAuthenticationFilte myUsernamePasswordAuthenticationFilte = new MyUsernamePasswordAuthenticationFilte(redisService);return myUsernamePasswordAuthenticationFilte;
}

我在加两个处理器,当我登陆成功或者失败,由我自己弄; 登陆成功处理器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

response.setContentType("application/json;charset=UTF-8");final PrintWriter writer = response.getWriter();
writer.write("{\"code\":\"200\",\"msg\":\"登录成功\"}");
writer.close();
}
}

登陆失败的处理器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");final PrintWriter writer = response.getWriter();if(exception.getMessage().equals("坏的凭证")){
writer.write("{\"code\":\"401\",\"msg\":\"登录失败,用户名或者密码有误\"}");
writer.close();
}else {
writer.write("{\"code\":\"401\",\"msg\":\"登录失败,"+exception.getMessage()+"\"}");
writer.close();
}

}
}
说明:

为什么要继承SimpleUrlAuthenticationFailureHandler和SimpleUrlAuthenticationSuccessHandler 而不是实现AuthenticationFailureHandler,还是那句话,为了少写代码少踩坑,我这里是返回json字符串,你也可以弄成重定向啥的,也比较容易。 config再改一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MyUsernamePasswordAuthenticationFilte getAuthenticationFilter(){
MyUsernamePasswordAuthenticationFilte myUsernamePasswordAuthenticationFilte = new MyUsernamePasswordAuthenticationFilte(redisService);
myUsernamePasswordAuthenticationFilte.setAuthenticationFailureHandler(new MyUrlAuthenticationFailureHandler());
myUsernamePasswordAuthenticationFilte.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
myUsernamePasswordAuthenticationFilte.setFilterProcessesUrl("/sign_in");return myUsernamePasswordAuthenticationFilte;
}

这里要注意 myUsernamePasswordAuthenticationFilte.setFilterProcessesUrl(“/sign_in”); 因为 http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);并不是替换掉UsernamePasswordAuthenticationFilter,而是和你自己添加的过滤器同时存在,security会根据url判断该走哪个过滤器,如果loginProcessingUrl还是“/login”的话走的是UsernamePasswordAuthenticationFilter,这里要留意一下。

现在再加需求,我不仅需要普通登录方式,我想其他登录方式; 实现方式:加多个认证器,每个认证器对应一种登录方式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyAuthenticationProvider  implements AuthenticationProvider {private UserDetailsService userDetailsService;private BCryptPasswordEncoder bCryptPasswordEncoder;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {//       这里写验证逻辑return null;
}@Overridepublic boolean supports(Class<?> aClass) {return false;
}
}

在改SecurityConfig

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Overrideprotected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login_page").passwordParameter("username").passwordParameter("password").loginProcessingUrl("/sign_in").permitAll()
.and().authorizeRequests().antMatchers("/test").hasRole("test").anyRequest().authenticated().and().csrf().disable();
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
}MyUsernamePasswordAuthenticationFilte getAuthenticationFilter(){
MyUsernamePasswordAuthenticationFilte myUsernamePasswordAuthenticationFilte = new MyUsernamePasswordAuthenticationFilte(redisService);
myUsernamePasswordAuthenticationFilte.setAuthenticationFailureHandler(new MyUrlAuthenticationFailureHandler());
myUsernamePasswordAuthenticationFilte.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
myUsernamePasswordAuthenticationFilte.setFilterProcessesUrl("/sign_in");
myUsernamePasswordAuthenticationFilte.setAuthenticationManager(getAuthenticationManager());return myUsernamePasswordAuthenticationFilte;
}MyAuthenticationProvider getMyAuthenticationProvider(){
MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider(userDetailService,new BCryptPasswordEncoder());return myAuthenticationProvider;
}DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
daoAuthenticationProvider.setUserDetailsService(userDetailService);return daoAuthenticationProvider;
}protected AuthenticationManager getAuthenticationManager()  {
ProviderManager authenticationManager = new ProviderManager(Arrays.asList(getMyAuthenticationProvider(),daoAuthenticationProvider()));return authenticationManager;
}

我再加需求: 根据不同的客服端做不同的鉴权策略; 实现方式:加投票器;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyExpressionVoter extends WebExpressionVoter {@Overridepublic int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {//        这里写鉴权逻辑return 0;
}
}

再次改动config

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Overrideprotected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login_page").passwordParameter("username").passwordParameter("password").loginProcessingUrl("/sign_in").permitAll()
.and().authorizeRequests().antMatchers("/test").hasRole("test").anyRequest().authenticated().accessDecisionManager(accessDecisionManager()).and().csrf().disable();
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
}MyUsernamePasswordAuthenticationFilte getAuthenticationFilter(){
MyUsernamePasswordAuthenticationFilte myUsernamePasswordAuthenticationFilte = new MyUsernamePasswordAuthenticationFilte(redisService);
myUsernamePasswordAuthenticationFilte.setAuthenticationFailureHandler(new MyUrlAuthenticationFailureHandler());
myUsernamePasswordAuthenticationFilte.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
myUsernamePasswordAuthenticationFilte.setFilterProcessesUrl("/sign_in");
myUsernamePasswordAuthenticationFilte.setAuthenticationManager(getAuthenticationManager());return myUsernamePasswordAuthenticationFilte;
}MyAuthenticationProvider getMyAuthenticationProvider(){
MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider(userDetailService,new BCryptPasswordEncoder());return myAuthenticationProvider;
}DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
daoAuthenticationProvider.setUserDetailsService(userDetailService);return daoAuthenticationProvider;
}protected AuthenticationManager getAuthenticationManager()  {
ProviderManager authenticationManager = new ProviderManager(Arrays.asList(getMyAuthenticationProvider(),daoAuthenticationProvider()));return authenticationManager;
}public AccessDecisionManager accessDecisionManager(){
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(new MyExpressionVoter(),new WebExpressionVoter(),new RoleVoter(),new AuthenticatedVoter());return new UnanimousBased(decisionVoters);
}

在加两个鉴权失败处理器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("{\"code\":\"403\",\"msg\":\"没有权限\"}");
writer.close();
}
}

再加一个登出处理器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {final PrintWriter writer = response.getWriter();

writer.write("{\"code\":\"200\",\"msg\":\"登出成功\"}");
writer.close();
}
}

最后修改SecurityConfig,最终模样为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredRedisService redisService;@AutowiredMyUserDetailService userDetailService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());
}@Overridepublic void configure(WebSecurity web) throws Exception {

web.ignoring().antMatchers("/resources/**/*.html", "/resources/**/*.js","/resources/**/*.css", "/resources/**/*.txt","/resources/**/*.png", "/**/*.bmp", "/**/*.gif", "/**/*.png", "/**/*.jpg", "/**/*.ico");//        super.configure(web);}@Overrideprotected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login_page").passwordParameter("username").passwordParameter("password").loginProcessingUrl("/sign_in").permitAll()
.and().authorizeRequests().antMatchers("/test").hasRole("test").anyRequest().authenticated().accessDecisionManager(accessDecisionManager())
.and().logout().logoutSuccessHandler(new MyLogoutSuccessHandler())
.and().csrf().disable();
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
}MyUsernamePasswordAuthenticationFilte getAuthenticationFilter(){
MyUsernamePasswordAuthenticationFilte myUsernamePasswordAuthenticationFilte = new MyUsernamePasswordAuthenticationFilte(redisService);
myUsernamePasswordAuthenticationFilte.setAuthenticationFailureHandler(new MyUrlAuthenticationFailureHandler());
myUsernamePasswordAuthenticationFilte.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
myUsernamePasswordAuthenticationFilte.setFilterProcessesUrl("/sign_in");
myUsernamePasswordAuthenticationFilte.setAuthenticationManager(getAuthenticationManager());return myUsernamePasswordAuthenticationFilte;
}MyAuthenticationProvider getMyAuthenticationProvider(){
MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider(userDetailService,new BCryptPasswordEncoder());return myAuthenticationProvider;
}DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
daoAuthenticationProvider.setUserDetailsService(userDetailService);return daoAuthenticationProvider;
}protected AuthenticationManager getAuthenticationManager()  {
ProviderManager authenticationManager = new ProviderManager(Arrays.asList(getMyAuthenticationProvider(),daoAuthenticationProvider()));return authenticationManager;
}public AccessDecisionManager accessDecisionManager(){
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(new MyExpressionVoter(),new WebExpressionVoter(),new RoleVoter(),new AuthenticatedVoter());return new UnanimousBased(decisionVoters);

}
}

你可能想要用jwtToken 做token鉴权的方式; 也好做,我这里提一下思路,鉴权都是在投票器里面,那我们在投票器之前填充好securitycontext就成,然后实现一个自己的投票器;填充securitycontext随便找个过滤器http.addFilterAfter() 然后在过滤器里面填充好就行,至于拿token的接口,在config里用.permitAll()放行就行了,比上面的改法还简单,我就不写了。

题外话

具体的代码可以参考我的项目poseindon,这种security改动方式我经过生产实践的,不会有问题。另外篇幅有点长 感谢大佬的阅读。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 六个核弹 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
JVM难学?那是因为你没认真看完这篇文章
JAVA程序运行与虚拟机之上,运行时需要内存空间。虚拟机执行JAVA程序的过程中会把它管理的内存划分为不同的数据区域方便管理。
美的让人心动
2018/09/20
3600
JVM难学?那是因为你没认真看完这篇文章
JVM底层知识备忘
今天的工作涉及了不少JVM底层的知识,趁着今天刚翻阅资料,还记得一些内容,将我常用的JVM知识整理一下。 JVM组成 JVM组成 JVM = 类加载器 classloader + 执行引擎 execu
jeremyxu
2018/05/10
8090
《深入理解Java虚拟机》读书笔记
-Xmn: 可以设置新生代的大小,设置一个比较大的新生代会减少老年代的大小,这个设置对系统性能以及GC行为有很大的影响,新生代大小一般会设置整个堆空间的1/3到1/4左右。
用户5640963
2019/07/26
4970
《深入理解Java虚拟机》读书笔记
【修订完善版】面试又被 JVM 垃圾回收虐了?推荐你看看这篇文章!
当需要排查各种内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
Guide哥
2020/05/07
5700
【修订完善版】面试又被 JVM 垃圾回收虐了?推荐你看看这篇文章!
2019年JVM最新面试题,必须收藏它
方法区和对是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。
李红
2019/08/22
6420
超长JVM总结,面试必备
JVM 是可运行 Java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收,堆 和 一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接的交互。
java金融
2020/08/05
6280
超长JVM总结,面试必备
JVM原理与深度调优
jvm是java虚拟机 运行在用户态、通过应用程序实现java代码跨平台、与平台无关、实际上是"一次编译,到处执行"
IT大咖说
2021/06/15
4040
JVM原理与深度调优
JVM 知识点全面梳理
JDK7内存模型(图来自于网络):
BUG弄潮儿
2020/12/02
3650
JVM知识点精华汇总
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/details/81490570
DannyHoo
2018/09/13
6540
JVM知识点精华汇总
JVM第一弹
JVM是可运行java代码的假想计算机,包括一套字节码指令集,一组寄存器,一个栈,一个垃圾回收、堆和一个存储方法域。JVM是运行在操作系统之上的,它与硬件没有直接的交互。
趣学程序-shaofeer
2020/02/03
2830
【深入理解JVM】详解GC日志参数配置及日志分析
2019-07-31T01:49:29.633+0800: 0.086: [GC (Allocation Failure) [PSYoungGen: 504K->488K(1024K)] 504K->504K(1536K), 0.0007589 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
程序员云帆哥
2022/05/12
1.5K0
【深入理解JVM】详解GC日志参数配置及日志分析
五位卷王 | 总结的十道 JVM 面试真题!(建议收藏)
先说下哈,这次给大家带来的大厂面试题,都来源于我的知识星球,由星球的几位嘉宾 + 我 一起完成。
悟空聊架构
2022/05/13
4670
五位卷王 | 总结的十道 JVM 面试真题!(建议收藏)
JVM经典20问!
线程私有的,作为当前线程的行号指示器,用于记录当前虚拟机正在执行的线程指令地址。程序计数器主要有两个作用:
程序员大彬
2022/07/08
4140
JVM经典20问!
JVM内存模型和垃圾回收机制
有的博客称方法区是永久代,那是因为前者是JVM的规范,而后者则是JVM规范的一种实现,并且只有HotSpot才有永久代,
全菜工程师小辉
2019/08/16
7220
JVM垃圾回收(GC)
对象被引用一次,在它的对象头上加一次引用次数,如果没有被引用(引用次数为 0),则此对象可回收
chenchenchen
2022/01/05
3170
JVM垃圾回收(GC)
jvm面试题2021_jvm运行原理及调优面试题
Jdk (Java Development Kit) : java语言的软件开发包。包括Java运行时环境Jre。 Jre (Java Runtime Environment) :Java运行时环境,包括Jvm。 Jvm (Java Virtual Machine) :一种用于计算机设备的规范。
全栈程序员站长
2022/09/27
3160
jvm面试题2021_jvm运行原理及调优面试题
一文理清JVM和GC 【第二弹】
程序在垃圾回收上花费了98%的时间,却收集不会2%的空间。 假如不抛出GC overhead limit,会造成:
蔡不菜丶
2020/11/11
4760
一文理清JVM和GC 【第二弹】
Java工程师成神之路(一)之jvm基础篇
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
哲洛不闹
2018/09/14
5510
Java工程师成神之路(一)之jvm基础篇
满意offer之JVM虚拟机问答汇总
Java 源文件,通过编译器,能够生产相应的.Class 文件,也就是字节码文件, 而字节码文件又通过 Java 虚拟机中的解释器,编译成特定机器上的机器码 。
蒋老湿
2019/07/31
4610
JVM的入门知识
前言:巴拉巴拉,今天给大家分享一点java三剑客(jre,jvm,jdk)中的jvm,纯理论教科书篇。 非原创,里面摘取了多个博客里面的内容
王念博客
2020/08/14
4420
相关推荐
JVM难学?那是因为你没认真看完这篇文章
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 从一个基础的springsecurity开始,进行代码跟踪分析其原理
  • springsecurity 的默认登陆url是/login:我们访问一下来捋一捋:
  • 说明:
  • 说明:
  • security认证原理
  • security的投票器:
  • security魔改
  • 说明:
  • 题外话
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档