SpringSecurity(九)从请求对象获取登录数据

上两篇我们主要讨论了一下从SecurityContextHolder中获取数据以及他的原理,这里我们来看一下第二种方法获取请登录数据,从当前请求种对象种获取。

从请求对象中获取

我们首先来看一下获取登录数据的代码,如下:

@GetMapping("/authentication")
    public void authentication(Authentication authentication){
        System.out.println("authentication = "+ authentication);
    }

    @GetMapping("/principal")
    public void principal(Principal principal){
        System.out.println("principal = " + principal);
    }

获取代码如上,我们可以直接在Controller的请求参数中放入Authenticaion对象来获取登录用户信息,通过前面文章的分析,我们已经知道Authentication是Principal的子类,所以也可以直接在请求参数中放入Principal来接收当前登录用户信息。需要注意的是,即使参数是Principal,真正的实例依然是Authentication的实例。

用过MVC的都知道,Controller中方法的参数都是当前请求HttpServletRequest带来的,毫无疑问,前面的Authenticaion和Principal参数也都是HttpServletRequest带来的,那么这里就存在两个问题了。

(1)、这些登录用户数据是何时放入HttpServletRequest的?

(2)、这些数据又是以何种形式存在的?

我们来看看:


在Servlet规范中,最早由三个和安全管理相关的方法:

//1.用来获取登录用户名
public String getRemoteUser();

//2.用来判断当前登录用户是否具备某一个指定的角色
public boolean isUserInRole(String role);

//3.用来获取当前认证主体
public java.security.Principal getUserPrincipal()

从Servlet3.0开始,又在这三个方法的基础上,又增加了三个和安全管理相关的方法

//1.判断当前请求是否认证成功。
public boolean authenticate(HttpServletResponse response) throws IOException,ServletException;
    
//2.可以执行登录操作
public void login(String username,String password) throws ServletException;

//3.可以执行注销操作
public void logout() throws ServletException;

不过HttpServletRequest只是一个接口,这些安全认证相关的方法,在不同环境下会有不同的实现。

如果是一个普通的Web项目,不使用任何框架,HttpServletRequest的默认实现类是Tomcat中的RequestFacade,从这个类的名字上就可以看出来,这是一个用了Facade模式(外观模式)的类,真正提供底层服务的是Tomcat中的Request对象,只不过这个Request对象在实现Servlet规范的同时,还定义了很多Tomcat内部的方法,为了避免我们直接调用到这些内部方法,这里使用了外观模式。

在Tomcat的Request类中,对上面这些方法都做了实现,基本上都是基于Tomcat提供的Realm来实现的,这种认证方法非常冷门,项目中也很少使用,因此这里不做介绍。

如果使用了SpringSecurity框架,那么我们在Controller参数中拿到的HttpServletRequest实例将是Servlet3SecurityContextHolderAwareRequestWrapper,很明显,这是被SpringSecurity封装过的请求。

我们来看一下Servlet3SecurityContextHolderAwareRequestWrapper的继承关系。

在这里插入图片描述

HttpServletRequestWrapper就不用过多介绍了,有兴趣的可以看看前几篇文章,SecurityContextHolderAwareRequestWrapper类主要实现了Servlet3.0之前和安全管理相关的三个方法,也就是getRemoteUser()、isUserInRole()以及getUserPrincipal()。Servlet3.0中新增的三个安全管理相关的方法则在Servlet3SecurityContextHolderAwareRequestWrapper类中实现。获取用户登录信息主要和前面三个方法有关,因此我们主要来看一下

SecurityContextHolderAwareRequestWrapper类中相关方法的实现。

public class SecurityContextHolderAwareRequestWrapper extends HttpServletRequestWrapper {

	private final AuthenticationTrustResolver trustResolver;

	
	private final String rolePrefix;

	
	public SecurityContextHolderAwareRequestWrapper(HttpServletRequest request, String rolePrefix) {
		this(request, new AuthenticationTrustResolverImpl(), rolePrefix);
	}

	
	public SecurityContextHolderAwareRequestWrapper(HttpServletRequest request,
			AuthenticationTrustResolver trustResolver, String rolePrefix) {
		super(request);
		Assert.notNull(trustResolver, "trustResolver cannot be null");
		this.rolePrefix = rolePrefix;
		this.trustResolver = trustResolver;
	}

	
	private Authentication getAuthentication() {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		return (!this.trustResolver.isAnonymous(auth)) ? auth : null;
	}

	
	@Override
	public String getRemoteUser() {
		Authentication auth = getAuthentication();
		if ((auth == null) || (auth.getPrincipal() == null)) {
			return null;
		}
		if (auth.getPrincipal() instanceof UserDetails) {
			return ((UserDetails) auth.getPrincipal()).getUsername();
		}
		if (auth instanceof AbstractAuthenticationToken) {
			return auth.getName();
		}
		return auth.getPrincipal().toString();
	}

	
	@Override
	public Principal getUserPrincipal() {
		Authentication auth = getAuthentication();
		if ((auth == null) || (auth.getPrincipal() == null)) {
			return null;
		}
		return auth;
	}

	private boolean isGranted(String role) {
		Authentication auth = getAuthentication();
		if (this.rolePrefix != null && role != null && !role.startsWith(this.rolePrefix)) {
			role = this.rolePrefix + role;
		}
		if ((auth == null) || (auth.getPrincipal() == null)) {
			return false;
		}
		Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
		if (authorities == null) {
			return false;
		}
		for (GrantedAuthority grantedAuthority : authorities) {
			if (role.equals(grantedAuthority.getAuthority())) {
				return true;
			}
		}
		return false;
	}

	
	@Override
	public boolean isUserInRole(String role) {
		return isGranted(role);
	}

	@Override
	public String toString() {
		return "SecurityContextHolderAwareRequestWrapper[ " + getRequest() + "]";
	}

}

SecurityContextHolderAwareRequestWrapper类其实非常好理解:

(1)、getAuthentication:用来获取当前登录对象Authentication,获取方式就是上一篇说的SecurityContextHolder中获取,如果不是匿名对象就返回,否则返回null

(2)、getRemoteUser:返回了当前登录用户的用户名,如果Authentication对象中存储的Principal是当前登录用户对象,则返回用户,如果Authentication对象中存储的Principal是当前登录用户名,则直接返回即可。

(3)、isGranted:是一个私有方法,作用是判断当前登陆用户是否具备某一个指定的角色。判断的逻辑也很简单,先对传入进来的角色进行预处理,有的情况下可能需要添加ROLE_前缀。然后调用Authentication#getAuthorities方法,获取当前登录用户所具备的所有角色,最后在和传入进来的参数进行比较

(4)、isUserInRole:该方法调用isGranted方法,进而实现判断当前用户是否具备某一个指定角色的功能。

到这里我们应该就能明白,在使用了SpringSecurity之后,我们通过HttpServletRequest就可以获取到很多当前登录用户信息了,如下:

@GetMapping("/info")
    public void info(HttpServletRequest request){
        String remoteUser = request.getRemoteUser();
        Authentication auth = (Authentication) request.getUserPrincipal();
        boolean admin = request.isUserInRole("admin");
        System.out.println("remoteUser = "+ remoteUser);
        System.out.println("auth.getName() = " + auth.getName());
        System.out.println("admin = "+ admin);
    }

登录成功后,可以看到打印如下

remoteUser = test
auth.getName() = test
admin = true

前面我们直接将Authentication或者Principal写到Controller参数中,实际上就是SpringMVC框架从Servlet3SecurityContextHolderAwareRequestWrapper中提取到的用户信息。

那么SpringSecurity是如何将默认的请求对象转换为了Servlet3SecurityContextHolderAwareRequestWrapper的呢?这里就设计到SpringSecurity过滤器链中另外一个重要的过滤器-SecurityContextHolderAwareRequestFilter。

前面我们提到SpringSecurity过滤器中,有一个SecurityContextHolderAwareRequestFilter过滤器,该过滤器的主要作用就是对HttpServletRequest请求进行在包装,重写HttpServletRequest中和安全管理相关的方法。HttpServletRequest在整个请求过程中会被包装多次,每一次的包装都会给它增添新的功能。例如在经过SecurityContextPersistenceFilter请求时就会对它进行包装。

我们来看一下SecurityContextHolderAwareRequestFilter过滤器:

public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {

	private String rolePrefix = "ROLE_";

	private HttpServletRequestFactory requestFactory;

	private AuthenticationEntryPoint authenticationEntryPoint;

	private AuthenticationManager authenticationManager;

	private List<LogoutHandler> logoutHandlers;

	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

	public void setRolePrefix(String rolePrefix) {
		Assert.notNull(rolePrefix, "Role prefix must not be null");
		this.rolePrefix = rolePrefix;
		updateFactory();
	}

	
	public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
		this.authenticationEntryPoint = authenticationEntryPoint;
	}

	
	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

	
	public void setLogoutHandlers(List<LogoutHandler> logoutHandlers) {
		this.logoutHandlers = logoutHandlers;
	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		chain.doFilter(this.requestFactory.create((HttpServletRequest) req, (HttpServletResponse) res), res);
	}

	@Override
	public void afterPropertiesSet() throws ServletException {
		super.afterPropertiesSet();
		updateFactory();
	}

	private void updateFactory() {
		String rolePrefix = this.rolePrefix;
		this.requestFactory = createServlet3Factory(rolePrefix);
	}

	
	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
		Assert.notNull(trustResolver, "trustResolver cannot be null");
		this.trustResolver = trustResolver;
		updateFactory();
	}

	private HttpServletRequestFactory createServlet3Factory(String rolePrefix) {
		HttpServlet3RequestFactory factory = new HttpServlet3RequestFactory(rolePrefix);
		factory.setTrustResolver(this.trustResolver);
		factory.setAuthenticationEntryPoint(this.authenticationEntryPoint);
		factory.setAuthenticationManager(this.authenticationManager);
		factory.setLogoutHandlers(this.logoutHandlers);
		return factory;
	}

}

我们看到Filter中最主要的方法doFilter,这里会低矮用requestFactory.create方法对请求重新进行包装,requestFactory就是HttpServletRequestFactory类的实例,他的create方法礼包直接创建了一个Servlet3SecurityContextHolderAwareRequestWrapper实例。

对请求的HttpServletRequest包装之后,接下来在过滤器链中传递的HttpServletRequest对象,它的getRemoteUser()、isUserInRole()以及getUserPrincipal()方法就可以直接使用了。

HttpServletRequest中getUserPrincipal()方法有了返回值之后,最终在SpringMVC的ServletRequestMethodArgumentResolver#resolveArgument(Class<?>,HttpServletRequest)方法中进行默认参数解析,自动解析出Principal对象。我们在Controller中既可以通过Principal来接收参数,也可以通过Authentication对象来接收参数。

对于SpringSecurity中两种获取登录用户的方式以及一些基本的原理我们这里就介绍到这,如果不对还请不吝赐教。

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
Spring Security提供了很多自定义登录的方式,以下是一种常见的自定义登录流程: 1. 创建一个实现了UserDetailsService接口的自定义UserDetailsService类,用于从数据库或其他数据源中获取用户信息。该接口有一个loadUserByUsername方法,根据用户名加载用户信息并返回一个UserDetails对象。 2. 创建一个实现了PasswordEncoder接口的密码编码器类,用于对用户密码进行加密和验证。常见的实现类有BCryptPasswordEncoder和PasswordEncoder,可以根据项目需求选择合适的实现类。 3. 创建一个继承自WebSecurityConfigurerAdapter的配置类,并重写configure方法。在该方法中,可以配置登录页面、登录成功后的跳转页面、登录失败后的处理等。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/css/**", "/js/**", "/images/**").permitAll() // 静态资源放行 .antMatchers("/login").permitAll() // 登录页面放行 .anyRequest().authenticated() // 其他请求需要认证 .and() .formLogin() .loginPage("/login") // 自定义登录页面路径 .defaultSuccessUrl("/home") // 登录成功后的默认跳转路径 .failureUrl("/login?error") // 登录失败后的路径 .and() .logout() .logoutUrl("/logout") // 退出登录的路径 .logoutSuccessUrl("/login?logout") // 退出登录后的路径 .and() .csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } } ``` 4. 在登录页面的表单中,需要包含用户名和密码的输入框,并将表单提交到Spring Security提供的默认登录处理路径("/login")。 这样就完成了一个简单的Spring Security自定义登录流程。你可以根据项目需求进行更多的自定义配置,例如添加记住我功能、验证码等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈橙橙丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值