资讯详情

Spring Security 自定义资源认证规则

自定义资源认证规则

在这里插入图片描述

  • /index公共资源
  • /hello受保护资源

在项目中添加以下配置可以设置资源权限规则:

创建配置类,继承WebSecurityConfigurerAdapter,重写其中的configure(HttpSecurity http)该方法最终用于类别@Configuration.

@Configuration public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { 
              @Override     protected void configure(HttpSecurity http) throws Exception { 
                 http.authorizeHttpRequests()                 .mvcMatchers("/index")                 .permitAll()                 .anyRequest()                 .authenticated()                 .and()                 .formLogin();     } } 
# 说明 - permitAll() 该资源是公共资源,无需认证和授权即可直接访问 - anyRequest().authenticated()代表所有请求,必须经过认证才能访问 - formLogin() 代表打开表单认证 ## 注:放行资源必须放在所有认证请求之前! 

1.定制登录界面

@Configuration public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { 
              @Override     protected void configure(HttpSecurity http) throws Exception { 
                 http.authorizeHttpRequests()                 .mvcMatchers("/loginHtml").permitAll()  //请求路径,即Controller放行自定义登录界面的路径                 .mvcMatchers("/index").permitAll()   //请求路径/index放行
                .anyRequest().authenticated()   //其他的任何请求都要进行认证
                .and()
                .formLogin() //表单验证
                .loginPage("/loginHtml") //默认登录页面,也是通过请求进行跳转至默认登录页面
                .loginProcessingUrl("/doLogin")    //指定发送过来的/doLogin请求被捕获,进行权限验证。
                .usernameParameter("uname") //默认接收username参数,修改为uname
                .passwordParameter("passwd") //默认接收password参数,修改为passwd
// .successForwardUrl("/index") //认证成功 forward跳转路径 地址栏不变 每次都默认跳转到/index
                .defaultSuccessUrl("/index",true)   //认证成功之后的跳转 重定向
                // 如果之前保存了请求但是被拦截,在拦截之后先跳转到被保存的请求,如果没有,则跳转到指定的请求。
                // 如果第二个参数设置为true,则无论如何,只要认证成功,不管有没有保存的请求直接跳转到指定的请求。
                .and()
                .csrf().disable();   //禁止 csrf 跨域请求保护
    }
}

  • 登录表单method必须为post。

  • antion的请求路径与配置类中的loginProcessingUrl()一致。

  • 用户名密码的参数也需要与配置类中的usernameParameter()、passwordParameter()一致。

  • successForwardUrl、defaultSuccessUrl这个两个方法都可以实现成功之后跳转

    • successForwardUrl默认使用forward跳转 注意:不会跳转到之前请求路径
    • defaultSuccessUrl默认使用redirect跳转 注意:如果之前请求路径,会优先跳转之前请求路径,可以传入第二个参数进行修改。

2.自定义登录成功处理

有时候页面跳转并不能满足我们,特别是

public interface AuthenticationSuccessHandler { 
        
	/** * Called when a user has been successfully authenticated. * @param request the request which caused the successful authentication * @param response the response * @param authentication the <tt>Authentication</tt> object which was created during * the authentication process. */
	void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException;

}

根据接口的描述信息,也可以得知登录成功会自动回调这个方法,进一步查看它的默认类型,发现successForwardUrl、defaultSuccessUrl也是由它的子类实现的。

  • 自定义AuthenticationSuccessHandler实现
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { 
        
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { 
        
        Map<String,Object> result = new HashMap<String,Object>();
        result.put("msg","登录成功");
        result.put("status",200);
        response.setContentType("application/json;charset=UTF-8");
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}
  • 配置AuthenticationSuccessHandler
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { 
        

    @Override
    protected void configure(HttpSecurity http) throws Exception { 
        
        http.authorizeHttpRequests()
                //...
                .and()
                .formLogin() //表单验证
				//...
				//自定义登录成功处理
                .successHandler(new MyAuthenticationSuccessHandler())
                .failureForwardUrl("/loginHtml")
                .and()
                .csrf().disable();   //禁止 csrf 跨域请求保护
    }
}

3.显示登录失败信息

为了能够更直观在登录页面看到异常错误信息,可以在登录页面中直接获取异常信息。Spring Security在登录失败之后会将异常信息存储到request,session作用域中key为命名属性中。

源码可以参考:SimpleUrlAuthenticationFailureHandler

  • 显示异常信息

  • 配置
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { 
        

    @Override
    protected void configure(HttpSecurity http) throws Exception { 
        
        http.authorizeHttpRequests()
                //...
                .and()
                .formLogin() //表单验证
				//...
              //.failureForwardUrl("/loginHtml") //认证失败之后的forward跳转 forward -->异常信息存放在request
                .failureUrl("/loginHtml")  // 默认 认证失败之后的 redirect 跳转 redirect -->异常信息存放在session
                .and()
                .csrf().disable();   //禁止 csrf 跨域请求保护
    }
}

4.自定义登录失败处理

和自定义登录成功处理一样,Spring Security同样为前后端分离开发提供了登录失败的处理,这个类就是AuthenticationFailureHandler。

源码:

public interface AuthenticationFailureHandler { 
        

	/** * Called when an authentication attempt fails. * @param request the request during which the authentication attempt occurred. * @param response the response. * @param exception the exception which was thrown to reject the authentication * request. */
	void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException;

}

根据接口的描述信息,也可以得知登录失败会自动回调这个方法,进一步查看它的默认实现,发现failureUrl、failureForwardUrl也是由它的子类实现的。

  • 自定义AuthenticationFailureHandler实现
/* * 自定义登录失败解决方案 * */
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { 
        
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { 
        
        Map<String,Object> result = new HashMap<>();
        result.put("msg","登录失败:"+exception.getMessage());
        result.put("status",500);
        response.setContentType("application/json;charset=UTF-8");
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}

配置AuthenticationFailureHandler

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { 
        

    @Override
    protected void configure(HttpSecurity http) throws Exception { 
        
        http.authorizeHttpRequests()
				//...
                .and()
                .formLogin() //表单验证
				//...
               .successHandler(new MyAuthenticationSuccessHandler()) //用来自定义认证成功之后处理 前后端分离解决方案
// .failureForwardUrl("/loginHtml") //认证失败之后的forward跳转 forward -->异常信息存放在request
// .failureUrl("/loginHtml") // 默认 认证失败之后的 redirect 跳转 redirect -->异常信息存放在session
              .failureHandler(new MyAuthenticationFailureHandler()) //用来自定义认证失败之后处理 前后端分离解决方案
                .and()
                .csrf().disable();   //禁止 csrf 跨域请求保护
    }
}

5.注销登录

Spring Security中提供了默认的注销登录配置,在开发时也可以按照自己需求对注销进行个性化定制。

  • 开启注销登录

    @Configuration
    public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { 
              
    
        @Override
        protected void configure(HttpSecurity http) throws Exception { 
              
            http.authorizeHttpRequests()
    				//...
                    .and()
                    .formLogin() //表单验证
    				//...
                    .and()
                    .logout()   //开启注销登录 默认
                    .logoutUrl("/logout")  //指定注销登录 url 默认 默认请求方式:GET
                    .invalidateHttpSession(true) //默认 会话失效
                    .clearAuthentication(true)  //默认 清楚认证标记
                    .logoutSuccessUrl("/loginHtml") //注销登录成功之后跳转
                    .and()
                    .csrf().disable();   //禁止 csrf 跨域请求保护
        }
    }
    
    • 通过logout()方法注销配置
    • logoutUrl指定退出登录请求地址,默认是GET请求,路径为/logout
    • invalidateHttpSession退出时是否是session失效,默认值为true
    • clearAuthentication退出时是否清楚认证信息,默认值为true
    • logoutSuccessUrl退出登录时跳转地址
  • 配置多个注销登录请求

    如果项目中有需要,开发者还可以配置多个注销登录的请求,同时还可以指定请求的方法:

    @Configuration
    public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { 
              
    
        @Override
        protected void configure(HttpSecurity http) throws Exception { 
              
            http.authorizeHttpRequests()
    				//...
                    .and()
                    .formLogin() //表单验证
    				//...
                    .and()
                    .logout()   //开启注销登录 默认
                    .logoutRequestMatcher(new OrRequestMatcher(
                            new AntPathRequestMatcher("/aa","GET"),
                            new AntPathRequestMatcher("/bb","POST")
                    ))      //
                    .invalidateHttpSession(true) //默认 会话失效
                    .clearAuthentication(true)  //默认 清楚认证标记
                    .logoutSuccessUrl("/loginHtml") //注销登录成功之后跳转
                    .and()
                    .csrf().disable();   //禁止 csrf 跨域请求保护
        }
    }
    
  • 前后端分离注销登录配置

如果是前后端分离开发,注销成功之后就不需要页面跳转了,只需要将注销成功的信息返回前端即可,此时我们可以通过自定义LogoutSuccessHandler实现来返回内容注销之后信息:

public class MyLogoutSuccessHandler implements LogoutSuccessHandler { 
        
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { 
        
        Map<String,Object> result = new HashMap<String,Object>();
        result.put("msg","注销成功");
        result.put("status",200);
        response.setContentType("application/json;charset=UTF-8");
        String s = new ObjectMapper().writeValueAsString(result);
        response.getWriter().println(s);
    }
}

配置

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { 
        

    @Override
    protected void configure(HttpSecurity http) throws Exception { 
        
        http.authorizeHttpRequests()
				//...
                .and()
                .formLogin() //表单验证
				//...
                .and()
                .logout()   //开启注销登录 默认
                .logoutRequestMatcher(new OrRequestMatcher(
                        new AntPathRequestMatcher("/aa","GET"),
                        new AntPathRequestMatcher("/bb","POST")
                ))      //
                .invalidateHttpSession(true) //默认 会话失效
                .clearAuthentication(true)  //默认 清楚认证标记
                .logoutSuccessHandler(new MyLogoutSuccessHandler())  //注销登录成功之后处理
                .and()
                .csrf().disable();   //禁止 csrf 跨域请求保护
    }
}

6.登录用户数据获取

6.1.SecurityContextHolder

​ Spring Security会将登录用户数据保存在Session中。但是,为了使用方便,Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。当用户登录成功后,Spring Security会将登录成功的用户信息保存到SecurityContextHolder中。

​ SecurityContextHolder中的数据保存默认是通过ThreadLocal来实现的,使用ThreadLocal创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。当登录请求处理完毕后,Spring Security会将SecurityContextHolder中的数据拿出来保存到Session中,同时将SecurityContextHolder中的数据清空。以后每当有请求到来时,Spring Security就会先从Session中取出用户登录数据,保存到SecurityContextHolder中,方便在该请求的后续处理过程中使用,同时在请求结束时将SecurityContextHolder中的数据拿出来保存到Session中,然后将SecurityContextHolder中的数据清空。

实际那个SecurityContextHolder中存储的是SecurityContext,在SecurityContext中存储是Authentication。

这种设计是典型的策略设计模式:

public class SecurityContextHolder { 
        

	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";

	public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";

	public static final String MODE_GLOBAL = "MODE_GLOBAL";

	private static final String MODE_PRE_INITIALIZED = "MODE_PRE_INITIALIZED";

	public static final String SYSTEM_PROPERTY = "spring.security.strategy";

	private static String strategyName = System.getProperty(SYSTEM_PROPERTY);

	private static SecurityContextHolderStrategy strategy;

	//...

	private static void initializeStrategy() { 
        
		if (MODE_PRE_INITIALIZED.equals(strategyName)) { 
        
			Assert.state(strategy != null, "When using " + MODE_PRE_INITIALIZED
					+ ", setContextHolderStrategy must be called with the fully constructed strategy");
			return;
		}
		if (!StringUtils.hasText(strategyName)) { 
        
			// Set default
			strategyName = MODE_THREADLOCAL;
		}
		if (strategyName.equals(MODE_THREADLOCAL)) { 
        
			strategy = new ThreadLocalSecurityContextHolderStrategy();
			return;
		}
		if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) { 
        
			strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
			return;
		}
		if (strategyName.equals(MODE_GLOBAL)) { 
        
			strategy = new GlobalSecurityContextHolderStrategy();
			return;
		}
		// Try to load a custom strategy
		try { 
        
			Class<?> clazz = Class.forName(strategyName);
			Constructor<?> customStrategy = clazz.getConstructor();
			strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
		}
		catch (Exception ex) { 
        
			ReflectionUtils.handleReflectionException(ex);
		}
	}
	//...
}
  1. :这种存放策略是将SecurityContext存放在ThreadLocal中,Threadlocal的特点是在哪个线程中存储就要在哪个线程中读取,这其实非常适合web应用,因为在默认情况下,一个请求无论经过多少Filter到达Servlet,都是由一个线程来处理。这也是SecurityContextHolder的默认存储测录额,这种存储策略以为着如果在具体的业务处理代码中,开启了子线程,在子线程中去获取登录用户数据,就会获取不到
  2. :这种存储模式适用于多线程环境,如果希望在子线程中ue能够获取到登录用户数据,那么可以使用这种存储模式。
  3. :这种存储模式实际上是将数据保存在一个静态变量中,在JavaWeb开发中,这种模式很少使用到。

6.2.SecurityContextHolderStrategy

通过SecurityContextHolder可以得知,SecurityContextHolderStrategy接口用来定义存储策略方法

public interface SecurityContextHolderStrategy { 
        

	void clearContext();

	SecurityContext getContext();

	void setContext(SecurityContext context);

	SecurityContext createEmptyContext();

}

接口中一共定义了四个方法:

  • clearContext:该方法用来清除存储的SecurityContext对象。
  • getContext:该方法用来获取存储的SecurityContext对象。
  • setContext:该方法用来设置存储的SecurityContext对象。
  • create Empty Context:该方法则用来创建一个空的SecurityContext对象。

从上面可以看出每一个实现类对应一种策略的实现。

6.3.代码中获取认证之后用户数据

@RestController
public class HelloController { 
        

    
        标签: gn325blr固态继电器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台