[英]Pre-Authentication without Authorization using Spring Security
我的要求是:
在我的應用程序中,當用戶通過登錄屏幕(而不是使用Spring Security)登錄時,首次通過自定義的第三方API執行身份驗證。 現在,我們幾乎沒有使用其他服務調用的增強功能。 根據要求,在從我們的應用程序進行任何休息調用之前,我們需要針對數據庫重新驗證用戶。 由於用戶在使用登錄屏幕登錄時已經過驗證,並且請求中提供了這些詳細信息,因此我計划使用spring security來重新驗證用戶(預身份驗證方案)。我們沒有為此定義任何角色用戶在我們的應用程序 所以不必擔心角色。 我已閱讀參考手冊,但我沒有得到更多關於如何進一步的信息。 我理解的一件事是,在通過定制的第三方API進行身份驗證后,我們需要以某種方式告訴用戶彈簧上下文。 即使我谷歌搜索了一下,但無法得到一個符合我要求的好例子。 如果有人可以指導我如何開始一個例子,那將是很棒的。
我只需要告訴spring上下文類似“嘿......!此用戶已經過身份驗證,因此在用戶的憑據經過自定義的第三方API驗證后,可以允許他調用其余服務。
我不應該更改現有的初始身份驗證過程。 我應該只使用經過身份驗證的用戶信息,並進一步使用spring security來重新驗證用戶。
我的問題是類似於春季參考手冊中提到的問題http://docs.spring.io/spring-security/site/docs/3.0.x/reference/preauth.html
請不要用單行回答(除非它有一個合適的外部鏈接)。如果你能告訴我一個例子或偽代碼,那就太好了。
提前致謝。
我建議從Spring Security到第三方登錄頁面建立一個“橋梁”。 如果您使用Spring,我認為這是最好的工作方式。
這意味着,您有一個登錄處理程序,可以將用戶重定向到第三方登錄頁面。 登錄后,用戶將被重定向回Web應用程序。
你是這個意思嗎? 聽起來不錯嗎? 是否有意義?
如果是這樣,您可以使用我的文章獲得一些幫助:
<security:http entry-point-ref="legacyEntryPoint">
通常,這意味着只要相關的http調用嘗試訪問您的應用程序,這就是處理請求的入口點。 在您的情況下,legacyEntryPoint是您將實現的類,它將檢查用戶是否經過身份驗證; 如果沒有,它會將用戶重定向到第三方登錄系統,否則它會使用已知的“令牌”來使用您的應用程序。
希望有所幫助!
你試過這個嗎?
SecurityContextHolder.getContext().setAuthenticated(true);
。
對於第一次身份驗證,我希望您有權使用某些輸入值(如用戶名/密碼)調用第三方API,並返回true / false。 如果是這樣,您可以編寫自己的AuthenticationProvider,如下所示。 並調用您的第三方認證,如下所示。 Spring安全框架會自動設置SecurityContextHolder.getContext()。setAuthenticated(true或false); 因此。 你不必設置它。
public class MyAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String user = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_ONE"));
authorities.add(new SimpleGrantedAuthority("ROLE_TWO"));
UsernamePasswordAuthenticationToken authenticationToken = null;
if (<your 3rd party authentication result == true>)) {
authenticationToken = new UsernamePasswordAuthenticationToken(user, password, authorities);
} else {
throw new BadCredentialsException("Invalid credentials supplied. Please try again.");
}
return authenticationToken;
}
@Override
public boolean supports(Class<?> authentication) {
boolean supports = authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class);
return supports;
}
}
在進行進一步的REST API調用之前,在Spring控制器中,您可以使用代碼SecurityContextHolder.getContext()來檢查用戶是否有效.getAuthentication()。isAuthenticated();
您還可以使用下面的代碼獲取許多其他用戶信息。
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("getAuthorities : " + authentication.getAuthorities());
System.out.println("getName : " + authentication.getName());
System.out.println("getCredentials : " + authentication.getCredentials());
System.out.println("getDetails : " + authentication.getDetails());
System.out.println("getPrincipal : " + authentication.getPrincipal());
if (authentication.getPrincipal() instanceof User) {
User user = (User) authentication.getPrincipal();
System.out.println(user.getUsername());
System.out.println(user.getPassword());
System.out.println(user.getAuthorities());
}
我正在做一些非常相似的事情。 我正在為無狀態REST后端進行身份驗證,因此我希望用戶進行一次身份驗證,然后對於每個后續請求,身份驗證必須是透明的。 我正在使用令牌。 登錄時,用戶提供的憑據用於驗證和生成令牌(盡管最終,我們希望使用外部服務來獲取令牌)。 令牌作為標頭返回。 然后angularjs前端在每個后續REST調用上發送令牌。 后端檢查令牌的有效性,如果它是好的,則標記'已驗證'為真。
這是我的security-context.xml:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<http use-expressions="true"
entry-point-ref="restAuthenticationEntryPoint"
create-session="stateless">
<intercept-url pattern="/secured/extreme/**" access="hasRole('ROLE_SUPERVISOR')"/>
<intercept-url pattern="/secured/**" access="isAuthenticated()" />
<intercept-url pattern="/j_spring_security_check" requires-channel="https" access="permitAll"/>
<intercept-url pattern="/logon.jsp" requires-channel="https" access="permitAll"/>
<sec:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
</http>
<beans:bean id="restAuthenticationEntryPoint" class="com.company.project.authentication.security.RestAuthenticationEntryPoint" />
<beans:bean id="authenticationTokenProcessingFilter" class="com.company.project.authentication.security.AuthenticationTokenProcessingFilter" >
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="userDetailsServices">
<beans:list>
<beans:ref bean="inMemoryUserDetailsService" />
<beans:ref bean="tmpUserDetailsService" />
</beans:list>
</beans:property>
</beans:bean>
<beans:bean id="tmpUserDetailsService" class="com.company.project.authentication.security.TokenUserDetailsServiceImpl" />
<user-service id="inMemoryUserDetailsService">
<user name="temporary" password="temporary" authorities="ROLE_SUPERVISOR" />
<user name="user" password="userPass" authorities="ROLE_USER" />
</user-service>
<authentication-manager alias="authenticationManager">
<!-- Use some hard-coded values for development -->
<authentication-provider user-service-ref="inMemoryUserDetailsService" />
<authentication-provider ref='companyLdapProvider' />
</authentication-manager>
對於身份驗證過濾器,我將UsernamePasswordAuthenticationFilter子類化。 當它是登錄請求時,則會發生身份驗證提供程序的身份驗證,然后生成令牌。 如果從標頭中讀取令牌,則檢查令牌以進行身份驗證。 這是我的身份驗證過濾器(它仍然不是生產就緒的,但它可以幫助您了解您可以執行的操作):
public class AuthenticationTokenProcessingFilter extends UsernamePasswordAuthenticationFilter {
//~ Static fields/initializers =====================================================================================
private static final String HEADER_AUTH_TOKEN = "X-Auth-Token";
private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationTokenProcessingFilter.class);
private List<UserDetailsService> userDetailsServices = new ArrayList<UserDetailsService>();
//~ Constructors ===================================================================================================
public AuthenticationTokenProcessingFilter() {
super();
}
//~ Methods ========================================================================================================
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String authToken = this.extractAuthTokenFromRequest(request);
if (authToken == null) {
super.doFilter(request, res, chain);
return;
}
String userName = TokenUtils.getUserNameFromToken(authToken);
if (userName != null) {
UserDetails userDetails = loadUserByUsername(userName);
if (TokenUtils.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
UsernamePasswordAuthenticationToken authRequest = authenticateWithForm(request, response);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
Authentication authentication = this.getAuthenticationManager().authenticate(authRequest);
if (authentication.isAuthenticated()) {
try {
String authToken = TokenUtils.createToken(obtainUsername(request), obtainPassword(request));
LOGGER.info("Setting HTTP header {} = {}", HEADER_AUTH_TOKEN, authToken);
response.addHeader(HEADER_AUTH_TOKEN, authToken);
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
LOGGER.info("authorities = {}", authorities);
// Now we should make an in-memory table of the token and userdetails for later use
} catch(Exception e) {
LOGGER.warn("Error creating token for authentication. Authorization token head cannot be created.", e);
}
}
return authentication;
}
protected UsernamePasswordAuthenticationToken authenticateWithForm(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
return authRequest;
}
private String extractAuthTokenFromRequest(HttpServletRequest httpRequest) {
/* Get token from header */
String authToken = httpRequest.getHeader(HEADER_AUTH_TOKEN);
/* If token not found get it from request parameter */
if (authToken == null) {
authToken = httpRequest.getParameter("token");
}
return authToken;
}
public List<UserDetailsService> getUserDetailsServices() {
return userDetailsServices;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsServices.add(userDetailsService);
}
public void setUserDetailsServices(List<UserDetailsService> users) {
if (users != null) {
this.userDetailsServices.clear();
this.userDetailsServices.addAll(users);
}
}
private UserDetails loadUserByUsername(String username) {
UserDetails user = null;
List<Exception> exceptions = new ArrayList<Exception>();
for (UserDetailsService service: userDetailsServices) {
try {
user = service.loadUserByUsername(username);
break;
} catch (Exception e) {
LOGGER.warn("Could not load user by username {} with service {}", username, service.getClass().getName());
LOGGER.info("Exception is: ",e);
exceptions.add(e);
}
}
if (user == null && !exceptions.isEmpty()) {
throw new AuthenticationException(exceptions.get(0));
}
return user;
}
}
我仍然在努力改進UserDetailsService。 通常,您可以使用身份驗證提供程序來獲取UserDetails,但由於我有一個無狀態應用程序,因此當我想驗證令牌時,我必須確定要使用哪個UserDetailsService。 我正在使用自定義代碼執行此操作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.