我的应用程序提供了RESTful WS。在正常情况下,它会要求用户登录,然后才允许用户调用任何业务函数。这部分在Spring Security中很典型。
但是,我们将集成一个遗留门户系统来提供SSO。用户可能已在门户中进行了身份验证。当用户通过门户访问我们的应用程序时,门户将使用令牌调用应用程序中的URL。我们的应用程序将需要使用该令牌检索(从数据库,使用存储的进程)用户ID等。当然,然后我们需要使用检索到的用户ID来查找应用程序中的授权。
我的第一个想法是有一个新的过滤器,并将其添加到springSecurityFilterChain过滤器链中,它将根据提供的单点登录令牌执行“身份验证”,并在正常的身份验证过滤器之前运行。我的方向对吗?在哪里可以找到有关如何添加到过滤器链的更多信息,以及默认的过滤器链是什么?我的初始代码通过使用<sec:http>标签打开了spring安全性,在链中添加这样的额外过滤器有多容易?
希望任何人都能给出一个关于如何实现这一点的提示。谢谢
发布于 2015-09-04 20:51:46
您可以创建一个新的安全领域:
<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>
<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" />以下是组件:
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");
}
}
}过滤器:
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;
}
}入口点:
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:
@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中:
<custom-filter before="BASIC_AUTH_FILTER" ref="myTokenObtainingFilter" />筛选器中的逻辑应该是,如果没有自定义头部,则继续链,如果有,则使用令牌填充身份验证,并将其向下传递。
https://stackoverflow.com/questions/32298663
复制相似问题