具有数据库授权的 Spring JAAS 身份验证

Posted

技术标签:

【中文标题】具有数据库授权的 Spring JAAS 身份验证【英文标题】:Spring JAAS Authentication with database authorization 【发布时间】:2015-10-12 18:07:24 【问题描述】:

我正在使用 Spring 安全 4.0。我的登录模块是在应用程序服务器中配置的,所以我必须使用 JAAS 进行身份验证,但我的用户详细信息存储在数据库中,因此一旦通过身份验证的用户对象将通过查询数据库创建。您能否让我知道如何实现这一点,即 LDAP 身份验证并从数据库加载用户详细信息。还有如何使用eh-cache缓存用户对象,以便在service/dao层访问用户对象。

【问题讨论】:

【参考方案1】:

这可以使用 CustomAuthentication Provider 来实现。以下是代码。

import java.util.Arrays;
import java.util.List;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authentication.jaas.JaasGrantedAuthority;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.sun.security.auth.UserPrincipal;

public class CustomAutenticationProvider extends DaoAuthenticationProvider implements AuthenticationProvider 
    private AuthenticationProvider delegate;

    public CustomAutenticationProvider(AuthenticationProvider delegate) 
        this.delegate = delegate;
    

    @Override
    public Authentication authenticate(Authentication authentication) 
        Authentication a = delegate.authenticate(authentication);

        if(a.isAuthenticated())
            a = super.authenticate(a);
        else
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        

        return a;
    

    private List<GrantedAuthority> loadRolesFromDatabaseHere(String name) 
        GrantedAuthority grantedAuthority =new JaasGrantedAuthority(name, new UserPrincipal(name));
        return Arrays.asList(grantedAuthority);
    

    @Override
    public boolean supports(Class<?> authentication) 
        return delegate.supports(authentication);
    

    /* (non-Javadoc)
     * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider#additionalAuthenticationChecks(org.springframework.security.core.userdetails.UserDetails, org.springframework.security.authentication.UsernamePasswordAuthenticationToken)
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
                    throws AuthenticationException 


        if(!authentication.isAuthenticated())
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));


    

DAOAuthentication 所需的用户详细信息

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import com.testjaas.model.User;
import com.testjaas.model.UserRepositoryUserDetails;


@Component
public class AuthUserDetailsService implements UserDetailsService 


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        System.out.println("loadUserByUsername called !!");
        com.testjaas.model.User user = new User();
        user.setName(username);
        user.setUserRole("ROLE_ADMINISTRATOR");
        if(null == user) 
            throw new UsernameNotFoundException("User " + username + " not found.");
        

        return new UserRepositoryUserDetails(user);
    



RoleGrantor - 这将是 Spring JAAS 身份验证所需的虚拟类

import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.springframework.security.authentication.jaas.AuthorityGranter;

public class RoleGranterFromMap implements AuthorityGranter 

    private static Map<String, String> USER_ROLES = new HashMap<String, String>();

    static 
        USER_ROLES.put("test", "ROLE_ADMINISTRATOR");
        //USER_ROLES.put("test", "TRUE");
    

    public Set<String> grant(Principal principal) 
        return Collections.singleton("DUMMY");
    

SampleLogin - 这应该替换为您的登录模块

import java.io.Serializable;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

public class SampleLoginModule implements LoginModule 

    private Subject subject;
    private String password;
    private String username;
    private static Map<String, String> USER_PASSWORDS = new HashMap<String, String>();

    static 
        USER_PASSWORDS.put("test", "test");
    

    public boolean abort() throws LoginException 
        return true;
    

    public boolean commit() throws LoginException 
        return true;
    

    public void initialize(Subject subject, CallbackHandler callbackHandler,
            Map<String, ?> sharedState, Map<String, ?> options) 
        this.subject = subject;

        try 
            NameCallback nameCallback = new NameCallback("prompt");
            PasswordCallback passwordCallback = new PasswordCallback("prompt",false);

            callbackHandler.handle(new Callback[]  nameCallback,passwordCallback );

            this.password = new String(passwordCallback.getPassword());
            this.username = nameCallback.getName();
         catch (Exception e) 
            throw new RuntimeException(e);
        
    

    public boolean login() throws LoginException 

        if (USER_PASSWORDS.get(username) == null
                || !USER_PASSWORDS.get(username).equals(password)) 
            throw new LoginException("username is not equal to password");
        

        subject.getPrincipals().add(new CustomPrincipal(username));
        return true;
    

    public boolean logout() throws LoginException 
        return true;
    

    private static class CustomPrincipal implements Principal, Serializable 
        private final String username;

        public CustomPrincipal(String username) 
            this.username = username;
        

        public String getName() 
            return username;
        
    


Spring XML 配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">
    <security:http auto-config="true">
        <security:intercept-url pattern="/*" access="isAuthenticated()"/>
    </security:http>
    <!-- <security:authentication-manager>
        <security:authentication-provider ref="jaasAuthProvider" />
    </security:authentication-manager> -->

    <bean id="userDetailsService" class="com.testjaas.service.AuthUserDetailsService"></bean>
    <bean id="testService" class="com.testjaas.service.TestService"/>
    <bean id="applicationContextProvider" class="com.testjaas.util.ApplicationContextProvider"></bean>

    <security:authentication-manager>
        <security:authentication-provider ref="customauthProvider"/>
    </security:authentication-manager>

    <bean id="customauthProvider" class="com.testjaas.security.CustomAutenticationProvider">
        <constructor-arg name="delegate" ref="jaasAuthProvider" />
        <property name="userDetailsService" ref="userDetailsService" />
    </bean>

    <bean id="jaasAuthProvider" class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider">
        <property name="loginConfig" value="classpath:pss_jaas.config" />
        <property name="authorityGranters">
            <list>
                <bean class="com.testjaas.security.RoleGranterFromMap" />
            </list>
        </property>
        <property name="loginContextName" value="JASSAuth" />
        <property name="callbackHandlers">
            <list>
                <bean class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler" />
                <bean class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler" />
            </list>
        </property>
    </bean>
</beans>

示例 jaas 配置

JASSAuth 
  com.testjaas.security.SampleLoginModule required;
;

【讨论】:

以上是关于具有数据库授权的 Spring JAAS 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

绕过对用户名/密码的 JConsole 要求 - 当使用带有 JMX 的 Jaas 自定义登录模块来处理授权和身份验证时

将 JAAS 用于具有 Spring 安全性的 LDAP 密码

与 Spring Security/Apache Shiro 相比,JAAS 的缺点是啥?

具有 CAS 身份验证和自定义授权的 Spring Security

为啥我的程序无法创建新的 JAAS 登录上下文?

JAAS + 来自数据库的身份验证