Spring Boot具有用于单点登录的只读会话功能。

huangapple go评论77阅读模式
英文:

Spring Boot with read-only session for single sign on

问题

我们有一个遗留的Spring应用程序(A)(未使用spring-boot),用于处理身份验证并使用spring-session将会话写入Redis(在Redis中存储为XML数据)。

现在我们想引入一个新的应用程序(B),使用spring-boot 2.2.6.RELEASE 和 spring-session Corn-RC1,如果用户已经在(A)中签入了ROLE_ADMIN,则应该可以使用它。也就是说,这可以被视为一种非常粗糙的单点登录方法。用户不应该能够在B中进行身份验证(如果可能的话,最好禁用身份验证),它只应该检查会话存储库(redis)中的现有用户是否已经通过身份验证,并具有ROLE_ADMIN。A和B都将位于同一个域下,因此浏览器会传播cookie。我尝试过多种不同的方法来使这个工作,例如:

@Configuration
@EnableWebSecurity
class ServiceBSpringSecurityConfig : WebSecurityConfigurerAdapter() {

    @Autowired
    fun configureGlobal(auth: AuthenticationManagerBuilder) {
        auth.inMemoryAuthentication()
    }

    override fun configure(http: HttpSecurity) {
        http
            .authorizeRequests()
                .anyRequest().hasRole("ADMIN")
                .and()
            .formLogin()
                .and()
            .httpBasic().disable()
    }
}

但是这会显示默认的登录界面:

Spring Boot具有用于单点登录的只读会话功能。

我还尝试过完全删除这部分:

@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder) {
    auth.inMemoryAuthentication()
}

但然后它会生成一个默认的user和密码,似乎不会调用configure方法(或者无论如何配置都不起作用)。

我该如何解决这个问题?

英文:

We have a legacy Spring application (A) (that is not using spring-boot) that handles authentication and writes the session to Redis using spring-session (the data in Redis is stored as XML).

We now want to introduce a new application (B), using spring-boot 2.2.6.RELEASE and spring-session Corn-RC1, that should be useable if a user has signed into (A) with ROLE_ADMIN. I.e. this can be regarded as a very crude way of doing single sign on. A user should never be able to authenticate in B (it'd like to disable authentication if possible), it should only check that an existing user is authenticated in the session repository (redis) and has ROLE_ADMIN. Both A and B will be located under the same domain so cookies will be propagated by the browser. I've tried various different ways of getting this to work, for example:

@Configuration
@EnableWebSecurity
class ServiceBSpringSecurityConfig : WebSecurityConfigurerAdapter() {

    @Autowired
    fun configureGlobal(auth: AuthenticationManagerBuilder) {
        auth.inMemoryAuthentication()
    }

    override fun configure(http: HttpSecurity) {
        http
            .authorizeRequests()
                .anyRequest().hasRole("ADMIN")
                .and()
            .formLogin()
                .and()
            .httpBasic().disable()
    }
}

but this will show the default login screen:

Spring Boot具有用于单点登录的只读会话功能。

I've also tried removing this part entirely:

@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder) {
    auth.inMemoryAuthentication()
}

but then it'll generate a default user and password and it does not seem to call the configure method (or the configuration doesn't work regardless).

How can I solve this?

答案1

得分: 1

你需要在应用程序 B 上禁用 formLoginhttpBasic,并在 Spring 的身份验证过滤器 AnonymousAuthenticationFilterUsernamePasswordAuthenticationFilter 之前添加一个过滤器。在自定义过滤器中,你将从请求对象中提取 cookie/header/token,并基于此信息访问 Redis 缓存以获取会话详细信息。然后,该过滤器将验证会话并创建类型为 org.springframework.security.core.Authentication 的对象,并将其设置在当前的 SpringSecurityContext 中。

以下是这个过程的伪代码示例:

ServiceBSpringSecurityConfig

@Configuration
@EnableWebSecurity
public class ServiceBSpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
            .exceptionHandling().authenticationEntryPoint(authEntryPoint()).and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .httpBasic().disabled().and()
            .formLogin().disabled().and()
            .authorizeRequests().anyRequest().hasRole("ADMIN");

        http.addFilterBefore(authTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public AuthTokenFilter authTokenFilter() {
        return new AuthTokenFilter();
    }

    @Bean
    public AuthEntryPoint authEntryPoint() {
        return new AuthEntryPoint();
    }
}

AuthEntryPoint

public class AuthEntryPoint implements AuthenticationEntryPoint {

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

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        // 非常通用的 authEntryPoint,只返回未授权
        // 还可以实现其他功能,比如转发到应用程序 A 的登录页面
        logger.error("Unauthorized error: {}", authException.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
    }
}

AuthTokenFilter

public class AuthTokenFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 从请求中提取某种类型的 token 或 cookie 值
        String token = request.getHeader("Token");
        if (token != null) {
            // 通过从 Redis 缓存检索会话来验证 token
            // 从 token 创建 org.springframework.security.core.Authentication
            Authentication auth = authFactory.getAuthentication(token);

            // 将认证信息设置到 Spring Security 上下文中
            SecurityContextHolder.getContext().setAuthentication(auth);
        } else {
            // 如果 token 不存在,则执行相应操作
        }
        // 继续执行过滤器链
        filterChain.doFilter(request, response);
    }
}

如前所述,这只是伪代码,可能需要进行一些调整。然而,基于令牌的身份验证的基本思路保持不变。

英文:

What you need is to disable formLogin and httBasic on Application B and add a filter before spring's authentication filter AnonymousAuthenticationFilter or UsernamePasswordAuthenticationFilter. In the custom filter you will extract the cookie/header/token from the request object and based on that reach out to the redis cache for session details. This filter would then validate the session and create object of type org.springframework.security.core.Authentication and set that in the current SpringSecurityContext.

Below is the sudo code for this;

ServiceBSpringSecurityConfig

@Configuration
@EnableWebSecurity
public class ServiceBSpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
	http.cors().and().csrf().disable()
		.exceptionHandling().authenticationEntryPoint(authEntryPoint()).and()
		.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .httpBasic().disabled().and()
        .formLogin().disabled().and()
		.authorizeRequests().anyRequest().hasRole("ADMIN")

	http.addFilterBefore(authTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
	public AuthTokenFilter authTokenFilter() {
    	return new AuthTokenFilter();
	}

    @Bean
    public AuthEntryPoint authEntryPoint() {
        return new AuthEntryPoint()
    }
}

AuthEntryPoint

public class AuthEntryPoint implements AuthenticationEntryPoint {

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

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        // Very generic authEntryPoint which simply returns unauthorized
        // Could implement additional functionality of forwarding the Application A login-page
        logger.error("Unauthorized error: {}", authException.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
    }
}

AuthTokenFilter

public class AuthTokenFilter extends OncePerRequestFilter {

	@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // extract some sort of token or cookie value from request
        token = request.getHeader("Token");
        if (token != null) {
            // Validate the token by retrieving session from redis cache
            // Create org.springframework.security.core.Authentication from the token
            Authentication auth = authFactory.getAuthentication(token);

            // Set the spring security context with the auth
            SecurityContextHolder.getContext().setAuthentication(auth);
        } else {
            // Do something if token not present at all
        }
        // Continue to to filter chain
        filterChain.doFilter(request, response);
    }
}

As mentioned this is sudo code so some adjustment might be required. However the general gist of token based auth remains the same.

huangapple
  • 本文由 发表于 2020年4月7日 17:32:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/61076901.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定