英文:
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()
}
}
但是这会显示默认的登录界面:
我还尝试过完全删除这部分:
@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:
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 上禁用 formLogin
和 httpBasic
,并在 Spring 的身份验证过滤器 AnonymousAuthenticationFilter
或 UsernamePasswordAuthenticationFilter
之前添加一个过滤器。在自定义过滤器中,你将从请求对象中提取 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论