現在、アプリケーションには認証と承認にLDAPを使用する単一の認証メカニズムがあります。私のセキュリティ構成は次のようになります
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
@Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
@Value("${ldap-${env}.manager.dn}")
private String managerDn;
@Value("${ldap-${env}.manager.pass}")
private String managerPass;
@Value("${ldap-${env}.server.url}")
private String url;
@Value("${ldap.password.attribute:userPassword}")
private String passwordAttr;
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userDnPatterns("uid={0},ou=people").groupSearchBase("ou=groups")
.groupSearchFilter("(member={0})").userSearchBase("ou=people").userSearchFilter("(uid={0})")
.userDetailsContextMapper(new CustomLdapPersonContextMapper())
// .passwordCompare()
// .passwordAttribute(passwordAttr)
// .passwordEncoder(new PlaintextPasswordEncoder())
// .and()
.contextSource().managerDn(managerDn).managerPassword(managerPass).url(url);
}
}
}
ただし、ユーザーがセッションキーサーバーから認証可能なセッショントークンを受け取り、有効なトークンがユーザー名を返し、そのユーザーの認証情報をLDAPから読み込むために使用できる状況があります。したがって、セッショントークンがhttpヘッダーに存在する場合はトークン認証を実行し、次にLDAPルックアップを実行する必要があり、セッショントークンが存在しない場合は現在の認証メカニズムに該当するという2番目の認証メカニズムが最初に発生します。この2番目の認証レイヤーを追加するにはどうすればよいですか。
純粋なJava構成。これを機能させるにはいくつかのステップがあります。これらのラインに沿ったものでなければなりません。基本的なプロセス以下のとおりであります:
特定の承認情報の要求を確認するカスタムフィルターを作成する
各フィルターは、null(そのタイプの許可が見つからない場合)またはカスタムAbstractAuthenticationTokenを返します
フィルターがトークンを返す場合、各AuthenticationProviderのsupports(class)メソッドが呼び出され、そのトークンが認証を試みる必要がある場合にtrue | falseを返します
その後、トークンをサポートするAuthenticationProviderでattemptAuthenticationが呼び出されます。ここでは、ユーザーを認証するためのサービス呼び出しを行います。その後、LoginExceptionをスローするか、authentication.setAuthenticated(true)を呼び出して、認証が成功した場合にトークンを返します。
私はしばらくの間、さまざまな認証方法(署名付きリクエスト、ユーザー名/パスワード、oauthなど)をサポートするこのセットアップを使用していました。
AuthenticationSuccessHandlerとAuthenticationFailuersHandlerをカスタムセキュリティフィルターに渡して、カスタムリダイレクト戦略と障害処理を提供することもできます。
また、フィルターのコンストラクターでAntマッチャーをセットアップして、フィルターが適用するURLパターンを制御することも忘れないでください。たとえば、LDAPリクエストフィルタは、おそらくリクエスト "/ *"で確認する必要がありますが、ユーザー名/パスワードフィルタは、POSTで/ loginなどに確認することができます。
サンプルコード:
1)サポートする認証の種類ごとにカスタムAuthenticationTokenを作成します
public class LDAPAuthorizationToken extends AbstractAuthenticationToken {
private String token;
public LDAPAuthorizationToken( String token ) {
super( null );
this.token = token;
}
public Object getCredentials() {
return token;
}
public Object getPrincipal() {
return null;
}
}
public class OTPAuthorizationToken extends UsernamePasswordAuthenticationToken {
private String otp;
public OTPAuthorizationToken( String username, String password, String otp ) {
super( username, password );
this.otp = otp;
}
public String getOTP() {
return otp;
}
}
2)タイプごとにカスタムセキュリティフィルターを作成する
public class LDAPAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private UserDetailsService userDetailsService;
public LDAPAuthorizationFilter() {
super( "/*" ); // allow any request to contain an authorization header
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
{
if ( request.getHeader( "Authorization" ) == null ) {
return null; // no header found, continue on to other security filters
}
// return a new authentication token to be processed by the authentication provider
return new LDAPAuthorizationToken( request.getHeader( "Authorization" ) );
}
}
public class OTPAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private UserDetailsService userDetailsService;
public OTPAuthorizationFilter() {
super( "/otp_login" );
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
{
if ( request.getParameter( "username" ) == null || request.getParameter( "password" ) == null || request.getParameter( "otp" ) == null ) {
return null;
}
// return a new authentication token to be processed by the authentication provider
return new OTPAuthorizationToken( request.getParameter( "username" ), request.getParameter( "password" ), request.getParameter( "otp" ) );
}
}
3)カスタムAuthenticationProviderを作成する
public class LDAPAuthenticationProvider implements AuthenticationProvider {
@Autowired
private MyAuthenticationService sampleService;
@Override
public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
LDAPAuthorizationToken auth = (LDAPAuthorizationToken)authentication;
String username = sampleService.verifyToken( auth.getCredentials() );
if ( username == null ) {
throw new LoginException( "Invalid Token" );
}
auth.setAuthenticated( true );
return auth;
}
@Override
public boolean supports( Class<?> authentication ) {
if ( authentication.isAssignableFrom( LDAPAuthorizationToken.class ) ) {
return true;
}
return false;
}
}
public class OTPAuthenticationProvider implements AuthenticationProvider {
@Autowired
private MyAuthenticationService sampleService;
@Override
public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
OTPAuthorizationToken auth = (OTPAuthorizationToken)authentication;
String error = sampleService.loginWithOTP( auth.getPrincipal(), auth.getCredentials(), auth.getOTP() );
if ( error != null ) {
throw new LoginException( error );
}
auth.setAuthenticated( true );
return auth;
}
@Override
public boolean supports( Class<?> authentication ) {
if ( authentication.isAssignableFrom( OTPAuthorizationToken.class ) ) {
return true;
}
return false;
}
}
4)スプリングセキュリティを構成する
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure( HttpSecurity http ) throws Exception {
// configure filters
http.addFilterBefore( new LDAPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );
http.addFilterBefore( new OTPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );
// configure authentication providers
http.authenticationProvider( new LDAPAuthenticationProvider() );
http.authenticationProvider( new OTPAuthenticationProvider() );
// disable csrf
http.csrf().disable();
// setup security
http.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and().httpBasic();
}
}
お役に立てば幸いです!
2番目の認証プロバイダーを追加する別のオプション:AuthenticationManagerBuilder
で別のプロバイダーを指定するだけです。なぜなら @EnableWebSecurity
アノテーション自体にEnableGlobalAuthentication
アノテーションが付けられます。AuthenticationManagerBuilder
のグローバルインスタンスを設定できます。 (詳細については javadocs を参照してください。)
たとえば、ここにはLDAP認証プロバイダーとメモリ内(ハードコードされた)認証プロバイダーがあります(これは開発中にローカルユーザーにテストを行うために行っていることです)。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${user.role}")
private String userRole; // i.e. ROLE_APP_USER
@Value("${include.test.users}")
private boolean includeTestUsers;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**/js/**").permitAll()
.antMatchers("/**/images/**").permitAll()
.antMatchers("/**/favicon.ico").permitAll()
.antMatchers("/**/css/**").permitAll()
.antMatchers("/**/fonts/**").permitAll()
.antMatchers("/**").hasAnyRole(userRole)
.and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();
http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, LdapContextSource contextSource) throws Exception {
auth.ldapAuthentication()
.userSearchBase("OU=Users OU")
.userSearchFilter("sAMAccountName={0}")
.groupSearchBase("OU=Groups OU")
.groupSearchFilter("member={0}")
.contextSource(contextSource);
if (includeTestUsers) {
auth.inMemoryAuthentication().withUser("user").password("u").authorities(userRole);
}
}
}
私はmclemaの答えに加えたいだけです。認証を成功させるにはオーバーライドを追加してフィルターチェーンを続行する必要があります。そうしないと、ユーザーは元のURL(例:/ myrest/server/somemethod)ではなくデフォルトのURL( "/")にリダイレクトされます。
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
SecurityContextHolder.setContext(context);
chain.doFilter(request, response);
}
受け入れられた答えには、現在のリクエストが許可されていないという問題があります。次のリクエストに対してのみセッションが確立されます!したがって、ポイント2で構成する必要がありました
public class MyAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
public MyAuthorizationFilter() {
super( "/*" ); // allow any request to contain an authorization header
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
{
if ( request.getHeader( "Authorization" ) == null ) {
return null; // no header found, continue on to other security filters
}
// required to use the token
myNewToken = new MyAuthorizationToken( request.getHeader( "Authorization" ) );
// and set in the current context ==> the current request is as well authorized
SecurityContextHolder.getContext().setAuthentication(myNewToken);
// return a new authentication token to be processed by the authentication provider
return myNewToken;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// try to authenticate the current request
attemptAuthentication((HttpServletRequest) req, (HttpServletResponse) res);
super.doFilter(req, res, chain);
}
}
それ以外セッションはすでに作成されていますが、現在のリクエストはまだ認証されていません!!! (そして、私が必要としないプロバイダー、つまりフィルターを追加するだけで十分です。)