Spring Security使用单点登录的权限功能

网友投稿 254 2022-08-16


Spring Security使用单点登录的权限功能

目录背景Spring Security实现已经有了单点登录页面,Spring Security怎么登录,不登录可以拿到权限吗Authentication继续分析结论如何手动登录Spring Security结论总结附参考

背景

在配置中心增加权限功能

目前配置中心已经包含了单点登录功能,可以通过统一页面进行登录,登录完会将用户写入用户表RBAC的用户、角色、权限表CRUD、授权等都已经完成希望不用用户再次登录,就可以使用SpringSecurity的权限控制

Spring Security

Spring Security最主要的两个功能:认证和授权

功能解决的问题Spring Security中主要类认证(Authentication)你是谁AuthenticationManager授权(Authorization)你可以做什么AuthorizationManager

实现

在这先简单了解一下Spring Security的架构是怎样的,如何可以认证和授权的

过滤器大家应该都了解,这属于Servlet的范畴,Servlet 过滤器可以动态地拦截请求和响应,以变换或使用包含在请求或响应中的信息

DelegatingFilterProxy是一个属于Spring Security的过滤器

通过这个过滤器,Spring Security就可以从Request中获取URL来判断是不是需要认证才能访问,是不是得拥有特定的权限才能访问。

已经有了单点登录页面,Spring Security怎么登录,不登录可以拿到权限吗

Spring Security官方文档-授权架构中这样说,GrantedAuthority(也就是拥有的权限)被AuthenticationManager写入Authentication对象,后而被AuthorizationManager用来做权限认证

The GrantedAuthority objects are inserted into the Authentication object by the AuthenticationManager and are later read by either the AuthorizationManager when making authorization decisions.

为了解决我们的问题,即使我只想用权限认证功能,也得造出一个Authentication,先看下这个对象:

Authentication

Authentication包含三个字段:

principal,代表用户credentials,用户密码authorities,拥有的权限

有两个作用:

AuthenticationManager的入参,仅仅是用来存用户的信息,准备去认证AuthenticationManager的出参,已经认证的用户信息,可以从SecurityContext获取

SecurityContext和SecurityContextHolder用来存储Authentication, 通常是用了线程全局变量ThreadLocal, 也就是认证完成把Authentication放入SecurityContext,后续在整个同线程流程中都可以获取认证信息,也方便了认证

继续分析

看到这可以得到,要实现不登录的权限认证,只需要手动造一个Authentication,然后放入SecurityContext就可以了,先尝试一下,大概流程是这样,在每个请求上

获取sso登录的用户读取用户、角色、权限写入Authentication将Authentication写入SecurityContext请求完毕时将SecurityContext清空,因为是ThreadLocal的,不然可能会被别的用户用到同时Spring Security的配置中是对所有的url都允许访问的

加了一个过滤器,代码如下:

import javax.servlet.*;

import javax.servlet.annotation.WebFilter;

import javax.servlet.http.HttpServletRequest;

import java.io.IOException;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.stream.Collectors;

@WebFilter( urlPatterns = "/*", filterName = "reqResFilter" )

public class ReqResFilter implements Filter{

@Autowired

private SSOUtils ssoUtils;

@Autowired

private UserManager userManager;

@Autowired

private RoleManager roleManager;

@Override

public void init( FilterConfig filterConfig ) throws ServletException{

}

@Override

public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain )

throws IOException, ServletException{

setAuthentication(servletRequest);

filterChain.doFilter( servletRequest, servletResponse );

clearAuthentication();

}

@Override

public void destroy(){

}

private void setAuthentication( ServletRequest request ){

Map data;

try{

data = ssoUtils.getLoginData( ( HttpServletRequest )request );

}

catch( Exception e ){

data = new HashMap<>();

data.put( "name", "visitor" );

}

String username = data.get( "name" );

if( username != null ){

userManager.findAndInsert( username );

}

List userRole = userManager.findUserRole( username );

List roleIds = userRole.stream().map( Role::getId ).collect( Collectors.toList() );

List rolePermission = roleManager.findRolePermission( roleIds );

List authorities = rolePermission.stream().map( one -> new SimpleGrantedAuthority( one.getName() ) ).collect(

Collectors.toList() );

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( username, "", authorities );

SecurityContextHolder.getContext().setAuthentication( authenticationToken );

}

private void clearAuthentication(){

SecurityContextHolder.clearContext();

}

}

从日志可以看出,Principal: visitor,当访问未授权的接口被拒绝了

16:04:07.429 [http-nio-8081-exec-9] DEBUG org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@cc4c6ea0: Principal: visitor; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: CHANGE_USER_ROLE, CHANGE_ROLE_PERMISSION, ROLE_ADD

...

org.springframework.security.access.AccessDeniedException: 不允许访问

结论

不登录是可以使用Spring Security的权限,从功能上是没有问题的,但存在一些别的问题

性能问题,每个请求都需要请求用户角色权限数据库,当然可以利用缓存优化我们写的过滤器其实也是Spring Security做的事,除此之外,它做了更多的事,比如结合HttpSession, Remember me这些功能

我们可以采取另外一种做法,对用户来说只登录一次就行,我们仍然是可以手动用代码再去登录一次Spring Security的

如何手动登录Spring Security

How to login user from java code in Spring Security? 从这篇文章从可以看到,只要通过以下代码即可

private void loginInSpringSecurity( String username, String password ){

UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken( username, password );

Authentication authenticatedUser = authenticationManager.authenticate( loginToken );

SecurityContextHolder.getContext().setAuthentication( authenticatedUser );

}

和上面我们直接拿已经认证过的用户对比,这段代码让Spring Security来执行认证步骤,不过需要配置额外的AuthenticationManager和UserDetailsServiceImpl,这两个配置只是AuthenticationManager的一种实现,和上面的流程区别不大,目的就是为了拿到用户的信息和权限进行认证

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.authority.SimpleGrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.core.userdetails.UsernameNotFoundException;

import org.springframework.stereotype.Service;

import java.util.List;

import java.util.stream.Collectors;

@Service

public class UserDetailsServiceImpl implements UserDetailsService{

private static final Logger logger = LoggerFactory.getLogger( UserDetailsServiceImpl.class );

@Autowired

private UserManager userManager;

@Autowired

private RoleManager roleManager;

@Override

public UserDetails loadUserByUsername( String username ) throws UsernameNotFoundException{

User user = userManager.findByName( username );

if( user == null ){

logger.info( "登录用户[{}]没注册!", username );

throw new UsernameNotFoundException( "登录用户[" + username + "]没注册!" );

}

return new org.springframework.security.core.userdetails.User( user.gHUMNowneetUsername(), "", getAuthority( username ) );

}

private List extends GrantedAuthority> getAuthority( String username ){

List userRole = userManager.findUserRole( username );

List roleIds = userRole.stream().map( Role::getId ).collect( Collectors.toList() );

List rolePermission = roleManager.findRolePermission( roleIds );

return rolePermission.stream().map( one -> new SimpleGrantedAuthority( one.getName() ) ).collect( Collectors.toList() );

}

}

@Override

@Bean

public AuthenticationManager authenticationManagerBean() throws Exception{

DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();

daoAuthenticationProvider.setUserDetailsService( userDetailsService );

daoAuthenticationProvider.setPasswordEncoder( NoOpPasswordEncoder.getInstance() );

return new ProviderManager( daoAuthenticationProvider );

}

结论

通过这样的方式,同样实现了权限认证,同时Spring Security会将用户信息和权限缓存到了Session中,这样就不用每次去数据库获取

总结

可以通过两种方式来实现不登录使用SpringSecurity的权限功能

手动组装认证过的Authentication直接写到SecurityContext,需要我们自己使用过滤器控制写入和清除手动组装未认证过的Authentication,并交给Spring Security认证,并写入SecurityContext

Spring Security是如何配置的,因为只使用权限功能,所有允许所有的路径访问(我们的单点登录会限制接口的访问)

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.security.authentication.AuthenticationManager;

import org.springframework.security.authentication.ProviderManager;

import org.springframework.security.authentication.dao.DaoAuthenticationProvider;

import org.springframework.security.config.annotation.method.confiHUMNowneguration.EnableGlobalMethodSecurity;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.crypto.password.NoOpPasswordEncoder;

import org.springframework.web.cors.CorsConfiguration;

import org.springframework.web.cors.CorsConfigurationSource;

import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

import java.util.Collections;

@Configuration

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

@Autowired

private UserDetailsService userDetailsService;

@Override

protected void configure( HttpSecurity http ) throws Exception{

http

.cors()

.and()

.csrf()

.disable()

.sessionManagement()

.and()

.authorizeRequests()

.anyRequest()

.permitAll()

.and()

.exceptionHandling()

.accessDeniedHandler( new SimpleAccessDeniedHandler() );

}

@Override

@Bean

public AuthenticationManager authenticationManagerBean() throws Exception{

DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();

daoAuthenticationProvider.setUserDetailsService( userDetailsService );

daoAuthenticationProvider.setPasswordEncoder( NoOpPasswordEncoder.getInstance() );

return new ProviderManager( daoAuthenticationProvider );

}

@Bean

public CorsConfigurationSource corsConfigurationSource(){

CorsConfiguration configuration = new CorsConfiguration();

configuration.setAllowedOrigins( Collections.singletonList( "*" ) );

configuration.setAllowedMethods( Arrays.asList( "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" ) );

configuration.setAllowCredentials( true );

configuration.setAllowedHeaders( Collections.singletonList( "*" ) );

configuration.setMaxAge( 3600L );

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

source.registerCorsConfiguration( "/**", configuration );

return source;

}

}

参考

Spring Security官方文档-授权架构How to login user from java code in Spring Security?


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java中Quartz高可用定时任务快速入门
下一篇:Java 超详细图解集合框架的数据结构
相关文章

 发表评论

暂时没有评论,来抢沙发吧~