Spring security only for authorization. External a

2019-05-06 16:19发布

As title says, i'm developing a web application that receives user authentication infos from an external application. A spring controller of my app gets user info and stores it in session. I want to authenticate this user inside Spring Security and then use his roles to grant/deny access to urls like

<intercept-url pattern="/myprotectedpage*" access="hasRole('rightrole')" />

I read some tutorials speaking about PRE_AUTH_FILTER and UserDetailsService but i can't get the point. What is the application lifecycle of Spring Security? Which classes are involved? I need some full working samples.

3条回答
Summer. ? 凉城
2楼-- · 2019-05-06 16:55

implement a service that holds the user information.

@Service
public class UserAuthenticationInfoService {

    private final Map<String, UserInfo> infos = new HashMap<String, UserInfo>();

    public void addUserInfo(UserInfo userInfo){
        infos.put(userInfo.getUsername(), userInfo);   
    }

    public UserInfo getUserInfo(String username) {
        return infos.get(username);
    }

}

your controllers populates the UserAuthenticationInfoService service with the user information which you receive from your external application.

then implement a custom UserDetailsService to acces these information.

public class CustomerUserDetailsService implements UserDetailsService {
    @Autowired
    private UserAuthenticationInfoService service;

     public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
        UserInfo info = service.getUserInfo(userName);
        return new User(info.getUsername(), info.getPassword(), info.getAuthorities());
    }
}

and setup spring security context to use this UserDetailsService (you'll find it in the spring security documentation)

查看更多
神经病院院长
3楼-- · 2019-05-06 17:04

You can implement your own Custom AuthenticationManager and Custom UsernamePasswordAuthenticationFilter. This is simple example but it can give you an idea also for your information this is very sensitive part of security context:)

Simply create beans in your spring_security.xml:

<http entry-point-ref="authenticationProcessingFilterEntryPoint"
        use-expressions="true">

        <custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
        <custom-filter ref="customUsernamePasswordAuthenticationFilter"
            position="FORM_LOGIN_FILTER" />

        <session-management
            session-authentication-strategy-ref="sas"></session-management>
    </http>


    <beans:bean id="authenticationProcessingFilterEntryPoint"
            class="org.springframework.security.web.authentication.AuthenticationProcessingFilterEntryPoint">
            <beans:property name="loginFormUrl" value="/login" />
        </beans:bean>

        <beans:bean id="sas"
            class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />

        <beans:bean id="customAuthenticationManager"
            class="my.package.security.CustomAuthenticationManager" />

        <beans:bean id="customUsernamePasswordAuthenticationFilter"
            class="my.package.security.CustomUsernamePasswordAuthenticationFilter">

            <beans:property name="sessionAuthenticationStrategy"
                ref="sas" />

            <beans:property name="authenticationManager" ref="customAuthenticationManager" />


            <beans:property name="authenticationSuccessHandler">

                <beans:bean
                    class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">

                    <beans:property name="defaultTargetUrl" value="/main.xhtml" />

                </beans:bean>

            </beans:property>

            <beans:property name="authenticationFailureHandler">

                <beans:bean
                    class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">

                    <beans:property name="defaultFailureUrl" value="/login.xhtml" />

                </beans:bean>

            </beans:property>
        </beans:bean>
<beans:bean id="sessionManagementFilter"
        class="org.springframework.security.web.session.SessionManagementFilter">
        <beans:constructor-arg name="securityContextRepository"
            ref="httpSessionSecurityContextRepository" />
    </beans:bean>
    <beans:bean id="httpSessionSecurityContextRepository"
        class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />

When you implement CustomUsernamePasswordAuthenticationFilter override Authentication and add your external logic:

public final class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

@Override
    public Authentication attemptAuthentication(HttpServletRequest request,   HttpServletResponse response){
CustomAuthentication auth = new CustomAuthentication();

            // set details of current user
            auth.setDetails(new WebAuthenticationDetails(request));
            auth.setAuthenticated(true);
            auth.setUserName(username);

            // set authentication to current security session
            LOGGER.info("Setting authentication into existing security context");
            SecurityContextHolder.getContext().setAuthentication(auth);

            // if validation done return generated authentication
            return auth;

}

}

Then generated authentication object will be handled by authentication manager:

public final class CustomAuthenticationManager implements AuthenticationManager {

    /*
     * (non-Javadoc)
     * 
     * @see org.springframework.security.authentication.AuthenticationManager#
     * authenticate(org.springframework.security.core.Authentication)
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomUsernamePasswordAuthenticationFilter.class);
    private final BadCredentialsException badCredentialsException = new BadCredentialsException("Invalid username/password");
    @Override
    public Authentication authenticate(Authentication authentication) {

        //check if user has valid authentication
        if (authentication == null) {
            LOGGER.debug("Null authentication");
            throw badCredentialsException;
        }
        //Check mandatory fields
        if (!Validator.isValidString((String) authentication.getPrincipal()) || !Validator.isValidString((String) authentication.getCredentials())) {
            LOGGER.debug("Null/blank username/credential");
            throw badCredentialsException;

        }
        //Check if there is any role assigned into user
        if (authentication.getAuthorities() != null && authentication.getAuthorities().size() < 1) {
            LOGGER.debug("No authority found");
            throw badCredentialsException;
        }

        //Validate role
        //IF ROLE VALIDATION REQUIRED YOU CAN HANDLE IT HERE
        boolean authorityValid = false;
        LOGGER.info("Validating user authentication. Total grantedAuth size: " + authentication.getAuthorities().size());
        for (GrantedAuthority g : authentication.getAuthorities()) {
            if (!authorityValid) {
                //Testing purpose one type role available, when exact roles prepared create enum types
                authorityValid = g.getAuthority().equals("ROLE_LDAP_AUTHENTICATED");
            }
        }

        //if none of role matching to required throw exception
        if(!authorityValid){
            LOGGER.debug("User has authority but none of them matching");
            throw badCredentialsException;
        }

        LOGGER.info("Final validation done returning authentication");
        return authentication;
    }

}

Then if required you can override default authentication object too,if roles dynamically located here is where you handle:

public final class CustomAuthentication implements Authentication {

        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        private transient String userName;
        private transient boolean authenticated;
        private transient Object details;
        private static final transient String ROLE = "ROLE_LDAP_AUTHENTICATED";

        /*
         * (non-Javadoc)
         * 
         * @see java.security.Principal#getName()
         */
        @Override
        public String getName() {
            return this.userName; //for dynamic username logic here
        }



     //IF ROLES DYNAMICALLY ALLOCATED ASSIGN IT HERE, HERE IS WHERE YOU READ FROM INTERCEPT URL

        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.security.core.Authentication#getAuthorities()
         */
        @Override
        public Collection<GrantedAuthority> getAuthorities() {
            Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
            auths.add(new GrantedAuthority() {

                /**
                 * 
                 */
                private static final long serialVersionUID = 1L;

                @Override
                public String getAuthority() {
                    if (authenticated) {

                        return ROLE;
                    }
                    return null;
                }
            });
            return auths;
        }

        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.security.core.Authentication#getCredentials()
         */
        @Override
        public Object getCredentials() {
            //TODO: a specific algorithm can be stored
            return userName + " is ldap authenticated user";
        }

        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.security.core.Authentication#getDetails()
         */
        @Override
        public Object getDetails() {
            return this.details;
        }

        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.security.core.Authentication#getPrincipal()
         */
        @Override
        public Object getPrincipal() {
            return userName;
        }

        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.security.core.Authentication#isAuthenticated()
         */
        @Override
        public boolean isAuthenticated() {

            return this.authenticated;
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * org.springframework.security.core.Authentication#setAuthenticated(boolean
         * )
         */
        @Override
        public void setAuthenticated(boolean arg0) {
            this.authenticated = arg0;

        }

        public String getUserName() {
            return userName;
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public void setDetails(Object details) {
            this.details = details;
        }

    }
查看更多
放荡不羁爱自由
4楼-- · 2019-05-06 17:10

There are lots of tuts out there for the same, just need to google properly.

Anyway the best i have found till date (for almost all spring tuts) is Krams and here's the one for basic spring security.

http://krams915.blogspot.com/2010/12/spring-security-mvc-integration_18.html

For Implementing UserDetailService here's the link

http://krams915.blogspot.in/2012/01/spring-security-31-implement_5023.html

Some others are :

  1. Spring By Example
  2. MK Young
  3. And SpringSource Site itself

EDIT

This is how my own application does the authentication (Please note that i dont use external authentication, I simpply get details from DB but i guess it should not be much of an issue).

My security-context.xml :

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <global-method-security pre-post-annotations="enabled" jsr250-annotations="enabled" secured-annotations="enabled">
    </global-method-security>

    <http use-expressions="true">
        <intercept-url pattern="/favicon.ico" access="permitAll" />
        <intercept-url pattern="/static/**" access="permitAll"/>
        <intercept-url pattern="/login.jsp*" access="permitAll"/> 
        <intercept-url pattern="/Admin/**" access="hasAnyRole('ROLE_SUPER_USER')"/>
        <intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_SUPER_USER','ROLE_ADMIN'"/>
        <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" />
        <http-basic/>
        <logout logout-success-url="/login.jsp"/>
        <remember-me user-service-ref="loginService" /
     </http>

    <authentication-manager>
        <authentication-provider user-service-ref="loginService">
         <password-encoder hash="md5"/>
        </authentication-provider>
    </authentication-manager>

    <beans:bean id="loginService" class="com.indyaah.service.LoginService">
    </beans:bean>
    <beans:bean id="authService" class="com.indyaah.service.AuthService" />
</beans:beans>

Now as you see i have specified a bean named loginService as my authentication provider which is a bean for class com.indyaah.service.LoginService.

The code for the same is : Pl Note I have truncated unnecessary code

package com.indyaah.service;
..
@Service
public class LoginService implements UserDetailsService {

....

    /**
     * Implementation for custom spring security UserDetailsService
     */
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
        logger.debug("Inside get member by username");
        if (userName != null) {
            Member memberVO = memberMapper.getMemberByUsername(userName);
            if (memberVO != null) {
                ArrayList<String> authList = memberRolesMapper.getMemberRoles(memberVO.getMemberId());

                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                for (String role : authList) {
                    System.out.println(role);
                    authorities.add(new GrantedAuthorityImpl(role.toString()));
                }

                if (memberVO.getEnabled()) {
                    User user = new User(memberVO.getUserName(), memberVO.getPassword(), true, true, true, true, authorities);
                    return user;
                } else {
                    logger.error("User with login: " + userName + " not Enabled in database. Authentication failed for user ");
                    throw new UsernameNotFoundException("User Not Enabled");
                }
            } else {
                logger.error("User with login: " + userName + " not found in database. Authentication failed for user ");
                throw new UsernameNotFoundException("user not found in database");
            }
        } else {
            logger.error("No User specified in the login ");
            throw new UsernameNotFoundException("No username specified");
        }

    }
}

Note 2 things over here.

  1. I get the user details (in my case from DB, yours may be diff.) and put it under a new org.springframework.security.core.userdetails.User object which is then returned by the method to spring security.
  2. Also the authorities, (which I load separately from DB as per my DB architecture, again your scenario may vary) and pass it to spring security via same User object.
查看更多
登录 后发表回答