首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Spring Security中遗留系统的单点登录

Spring Security中遗留系统的单点登录
EN

Stack Overflow用户
提问于 2015-08-31 00:19:51
回答 1查看 379关注 0票数 0

我的应用程序提供了RESTful WS。在正常情况下,它会要求用户登录,然后才允许用户调用任何业务函数。这部分在Spring Security中很典型。

但是,我们将集成一个遗留门户系统来提供SSO。用户可能已在门户中进行了身份验证。当用户通过门户访问我们的应用程序时,门户将使用令牌调用应用程序中的URL。我们的应用程序将需要使用该令牌检索(从数据库,使用存储的进程)用户ID等。当然,然后我们需要使用检索到的用户ID来查找应用程序中的授权。

我的第一个想法是有一个新的过滤器,并将其添加到springSecurityFilterChain过滤器链中,它将根据提供的单点登录令牌执行“身份验证”,并在正常的身份验证过滤器之前运行。我的方向对吗?在哪里可以找到有关如何添加到过滤器链的更多信息,以及默认的过滤器链是什么?我的初始代码通过使用<sec:http>标签打开了spring安全性,在链中添加这样的额外过滤器有多容易?

希望任何人都能给出一个关于如何实现这一点的提示。谢谢

EN

回答 1

Stack Overflow用户

发布于 2015-09-04 20:51:46

您可以创建一个新的安全领域:

代码语言:javascript
复制
<http use-expressions="true" pattern="/legacy-app/**" realm="Legacy App" create-session="stateless" disable-url-rewriting="true"
      authentication-manager-ref="tokenAuthenticationManager" entry-point-ref="tokenAuthenticationEntryPoint">
    <intercept-url pattern="/legacy-app/**" access="hasAuthority('LEGACY_USER_ROLE')" />
    <custom-filter ref="tokenAuthenticationFilter" position="FORM_LOGIN_FILTER"/>
    <logout logout-url="/legacy/logout" />
</http>
<authentication-manager id="tokenAuthenticationManager">
    <authentication-provider ref="tokenAuthenticationProvider" />
</authentication-manager>

代码语言:javascript
复制
<beans:bean id="tokenAuthenticationProvider" class="com.company.TokenAuthenticationProvider" >
    <beans:property name="userDetailsService" ref="userDetailsService"/>
    <beans:property name="passwordEncoder" ref="passwordEncoder"/>
</beans:bean>
<beans:bean id="tokenAuthenticationFilter" class="com.company.TokenAuthenticationFilter">
    <beans:constructor-arg index="0" ref="tokenAuthenticationManager" />
</beans:bean>
<bean id="userDetailsService" class="com.company.impl.UserDetailsServiceImpl" />

以下是组件:

代码语言:javascript
复制
public class TokenAuthenticationProvider extends DaoAuthenticationProvider {
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (!(userDetails instanceof User)) {
            throw new BadCredentialsException("Missing or invalid authentication token");
        }
        User user = (User) userDetails;

        if (!getPasswordEncoder().isPasswordValid(user.getToken(), authentication.getCredentials().toString())) {
            throw new BadCredentialsException("Missing or invalid authentication token");
        }

    }
}

过滤器:

代码语言:javascript
复制
    public class TokenAuthenticationFilter extends GenericFilterBean {

        private AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
        private AuthenticationEntryPoint authenticationEntryPoint;
        private AuthenticationManager authenticationManager;
        private RememberMeServices rememberMeServices = new NullRememberMeServices();
        private boolean ignoreFailure = true;
        private String credentialsCharset = "UTF-8";

        public TokenAuthenticationFilter() {
        }

        /**
         * Creates an instance which will authenticate against the supplied {@code AuthenticationManager}
         * and which will ignore failed authentication attempts, allowing the request to proceed down the filter chain.
         *
         * @param authenticationManager the bean to submit authentication requests to
         */
        public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
            this.authenticationManager = authenticationManager;
            ignoreFailure = true;
        }
    @Override
    public void afterPropertiesSet() {
        Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");

        if(!isIgnoreFailure()) {
            Assert.notNull(this.authenticationEntryPoint, "An AuthenticationEntryPoint is required");
        }
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        final boolean debug = logger.isDebugEnabled();
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;

        String header = request.getHeader("TOKEN");

        if (header == null) {
            chain.doFilter(request, response);
            return;
        }

        try {

            String username = "LEGACY-USER";
            String authToken = header.trim();

            if (debug) {
                logger.debug("TOKEN found, proceeding with token authentication");
            }

            if (authenticationIsRequired(username)) {
                UsernamePasswordAuthenticationToken authRequest =
                        new UsernamePasswordAuthenticationToken("ID:" + username, authToken);
                authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
                Authentication authResult = authenticationManager.authenticate(authRequest);

                if (debug) {
                    logger.debug("Authentication success: " + authResult);
                }

                SecurityContextHolder.getContext().setAuthentication(authResult);

                rememberMeServices.loginSuccess(request, response, authResult);

                onSuccessfulAuthentication(request, response, authResult);
            }

        } catch (AuthenticationException failed) {
            SecurityContextHolder.clearContext();

            if (debug) {
                logger.debug("Authentication request for failed: " + failed);
            }

            rememberMeServices.loginFail(request, response);

            onUnsuccessfulAuthentication(request, response, failed);

            if (ignoreFailure) {
                chain.doFilter(request, response);
            } else {
                authenticationEntryPoint.commence(request, response, failed);
            }

            return;
        }

        chain.doFilter(request, response);
    }



    private boolean authenticationIsRequired(String username) {
        // Only reauthenticate if username doesn't match SecurityContextHolder and user isn't authenticated
        // (see SEC-53)
        Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();

        if(existingAuth == null || !existingAuth.isAuthenticated()) {
            return true;
        }

        // Limit username comparison to providers which use usernames (ie UsernamePasswordAuthenticationToken)
        // (see SEC-348)

        if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) {
            return true;
        }

        // Handle unusual condition where an AnonymousAuthenticationToken is already present
        // This shouldn't happen very often, as BasicProcessingFitler is meant to be earlier in the filter
        // chain than AnonymousAuthenticationFilter. Nevertheless, presence of both an AnonymousAuthenticationToken
        // together with a BASIC authentication request header should indicate reauthentication using the
        // BASIC protocol is desirable. This behaviour is also consistent with that provided by form and digest,
        // both of which force re-authentication if the respective header is detected (and in doing so replace
        // any existing AnonymousAuthenticationToken). See SEC-610.
        if (existingAuth instanceof AnonymousAuthenticationToken) {
            return true;
        }

        return false;
    }
    protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              Authentication authResult) throws IOException {
    }

    protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                                AuthenticationException failed) throws IOException {
    }

    protected AuthenticationEntryPoint getAuthenticationEntryPoint() {
        return authenticationEntryPoint;
    }

    /**
     * @deprecated Use constructor injection
     */
    @Deprecated
    public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    protected AuthenticationManager getAuthenticationManager() {
        return authenticationManager;
    }

    /**
     * @deprecated Use constructor injection
     */
    @Deprecated
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    protected boolean isIgnoreFailure() {
        return ignoreFailure;
    }

    /**
     *
     * @deprecated Use the constructor which takes a single AuthenticationManager parameter
     */
    @Deprecated
    public void setIgnoreFailure(boolean ignoreFailure) {
        this.ignoreFailure = ignoreFailure;
    }

    public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource) {
        Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
        this.authenticationDetailsSource = authenticationDetailsSource;
    }

    public void setRememberMeServices(RememberMeServices rememberMeServices) {
        Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
        this.rememberMeServices = rememberMeServices;
    }

    public void setCredentialsCharset(String credentialsCharset) {
        Assert.hasText(credentialsCharset, "credentialsCharset cannot be null or empty");
        this.credentialsCharset = credentialsCharset;
    }

    protected String getCredentialsCharset(HttpServletRequest httpRequest) {
        return credentialsCharset;
    }
}

入口点:

代码语言:javascript
复制
public class TokenAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
    }
}

UserDetailsService:

代码语言:javascript
复制
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserRepository userRepository;

    @Resource(name = "applicationEncryptor")
    StringEncryptor encryptor;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    HttpServletRequest request;

    @Transactional
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException, DataAccessException {

        return getUserDetails(username);
    }

    private UserDetails getUserDetails(String username) {
        try {
            User user = null;
            user = userRepository.findByTokenID(username);

            return user;
        } catch (DataAccessException e) {
            throw new UsernameNotFoundException("DataAccessException - possibly duplicate username " + username + ".");
        }
    }

}

一些细节被省略了,但这基本上就是您的场景所需的一切。

编辑:这仅适用于/legacy-urls/

如果您想要无缝访问,您只需创建过滤器,并将其添加到BASIC_AUTH_FILTER之前的sec element中:

代码语言:javascript
复制
<custom-filter before="BASIC_AUTH_FILTER" ref="myTokenObtainingFilter" />

筛选器中的逻辑应该是,如果没有自定义头部,则继续链,如果有,则使用令牌填充身份验证,并将其向下传递。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32298663

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档