【问题标题】:How to set-up spring security without inMemoryAuthentication?如何在没有 inMemoryAuthentication 的情况下设置 Spring Security?
【发布时间】:2014-09-15 14:11:42
【问题描述】:

我的 Spring Security 已启动并正在运行。它使用我自己的身份验证提供程序,因为身份验证是由主机系统完成的。

因为我想拥有基于注释的安全性,所以我将@EnableGlobalMethodSecurity(securedEnabled = true) 添加到我的 servlet 配置中。

不幸的是,这触发了 AuthenticationManager is required 异常。避免此异常的唯一方法是将configureGlobal 方法添加到带有inMemoryAuthentication 的servlet 配置中。

显然我不想要这个 inMemoryAuthentication 因为应用程序依赖于我自己的主机身份验证。如何摆脱 inMemoryAuthentication?

这是我的 servlet 配置:

@Configuration
@EnableWebMvc
@EnableGlobalMethodSecurity(securedEnabled = true)
@Import({ 
    FooControllerConfig.class, 
})
public class FooServerletConfig {

    @Autowired
    //@formatter:off
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").authorities("ROLE_USER");
    }
    //@formatter:on

}

这是安全配置:

@Configuration
@EnableWebMvcSecurity
public class FooSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    //@formatter:off
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authenticationProvider(aS400AuthenticationProvider())
            .formLogin()
                .loginProcessingUrl("/authorized")
                .passwordParameter("password")
                .usernameParameter("clientId")
                .successHandler(fooAuthenticationSuccessHandler())
                .failureHandler(fooAuthenticationFailureHandler())
            .and()
            .csrf().disable()
            .rememberMe()
                .rememberMeServices(fooRememberMeServices())
                .key(CookieService.LIR_SESSION_COOKIE_NAME)
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint());
            ;
    }
    //@formatter:on

}

如果缺少configureGlobal 方法,这是我得到的异常:

SEVERE: Servlet /FOO threw load() exception
java.lang.IllegalArgumentException: An AuthenticationManager is required
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.afterPropertiesSet(AbstractSecurityInterceptor.java:121)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1571)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1509)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:296)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:293)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:633)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
    at org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:651)
    at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:508)
    at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:462)
    at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136)
    at javax.servlet.GenericServlet.init(GenericServlet.java:160)
    at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1280)
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1091)
    at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:5176)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5460)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:695)

【问题讨论】:

    标签: java spring spring-mvc spring-security


    【解决方案1】:

    为了其他有需要的人的利益。

    1。 JDBC 身份验证

    为了实现 jdbcAuthentication,您需要向数据库表写入两个查询
    Query1: usersByUsernameQuery(设置要使用的查询用于通过用户名查找用户。)
    Query2: authoritiesByUsernameQuery(设置用于按用户权限查找用户权限的查询用户名。)

    可以是java配置,也可以是xml配置,只需要
    1. 创建数据源
    2.在AuthenticationManagerBuilder中配置jdbc认证,注入datasource依赖,配置usersByUsernameQuery和authoritiesByUsernameQuery。
    3. 配置HttpSecurity。下面给出的详细信息和默认值
    ----- 为这些 url 配置拦截 url 模式和授权

    Default role of unauthenticated user = ROLE_ANONYMOUS
    

    ----- 配置表单登录以避免默认登录屏幕和下面给出的默认行为。

    login-page        = "/login" with HTTP get
    usernameParameter = "username"
    passwordParameter = "password"
    failureUrl        = "/login?error"
    loginProcessingUrl= "/login" with HTTP post
    successUrl        = "/"
    

    ----- 配置注销以覆盖默认行为。

    logoutUrl        = "/logout"
    logoutSuccessUrl = "/login?logout"
    

    ----- 配置会话管理以覆盖默认行为

    expiredUrl         = "/login?expired"
    invalidate-session = true //you can set false and use delete-cookies="JSESSIONID"
    maximumSessions    = The default is to allow any number of sessions for a users.
    
    

    Javaconfig方式

    如果这还不够,请从我的 github 存储库下载。 Working copy of Spring security with Java configuration

    @Configuration
    @EnableWebSecurity
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter 
    {
    
        public DataSource dataSource()
        {
            BasicDataSource dataSource = new BasicDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/springmvc");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
            dataSource.setInitialSize(2);
            dataSource.setMaxActive(5);
    
            return dataSource;
        }
    
    
        @Autowired
        public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception
        {
            auth.jdbcAuthentication().dataSource(dataSource()).passwordEncoder(passwordEncoder())
                    .usersByUsernameQuery("select username, password, enabled from userdetails where userName=?")
                    .authoritiesByUsernameQuery(
                            "select ud.username as username, rm.name as role from userdetails ud INNER JOIN rolemaster rm ON rm.id = ud.roleId  where username = ?");
        }
    
        @Override
        protected void configure(final HttpSecurity http) throws Exception
        {
            http
            .authorizeRequests()
                .antMatchers("/resources/**", "/", "/login", "/api/**").permitAll()
                .antMatchers("/config/*", "/app/admin/*")
                .hasRole("ADMIN")
                .antMatchers("/app/user/*")
                .hasAnyRole("ADMIN", "USER")
            .and().exceptionHandling()
                .accessDeniedPage("/403")
            .and().formLogin()
                .loginPage("/login")
                .usernameParameter("userName").passwordParameter("password")
                .defaultSuccessUrl("/app/user/dashboard")
                .failureUrl("/login?error=true")
            .and().logout()
                .logoutSuccessHandler(new CustomLogoutSuccessHandler())
                .invalidateHttpSession(true)
            .and()
                .csrf()
                    .disable();
    
            http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired=true");
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() 
        {
            return new BCryptPasswordEncoder();
        }
    
    }
    

    XML配置方式

    如果这还不够,请从我的 github 存储库下载。 Working copy of Spring security with XML configuration

    <?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.xsd  
        http://www.springframework.org/schema/security  
        http://www.springframework.org/schema/security/spring-security.xsd">  
    
        <http auto-config="true" use-expressions="true" create-session="ifRequired">
            <csrf disabled="true"/>
    
            <intercept-url pattern="/resources/**" access="permitAll" />
            <intercept-url pattern="/" access="permitAll" />
            <intercept-url pattern="/login" access="permitAll" />
            <intercept-url pattern="/api/**" access="permitAll" />
    
            <intercept-url pattern="/config/*" access="hasRole('ROLE_ADMIN')" />
            <intercept-url pattern="/app/admin/*" access="hasRole('ROLE_ADMIN')" />
    
            <intercept-url pattern="/app/user/*" access="hasAnyRole('ROLE_USER', 'ROLE_ADMIN')" />
    
            <access-denied-handler error-page="/403" />
    
            <form-login 
                login-page="/login" 
                default-target-url="/app/user/dashboard" 
                authentication-failure-url="/login?error=true" 
                username-parameter="userName"
                password-parameter="password" />
    
            <logout invalidate-session="false" success-handler-ref="customLogoutSuccessHandler"/>
    
            <session-management invalid-session-url="/login?expired=true">
                <concurrency-control max-sessions="1" />
            </session-management>
    
        </http>
    
    
        <authentication-manager>
          <authentication-provider>
            <password-encoder ref="encoder" /> 
            <jdbc-user-service data-source-ref="dataSource"
              users-by-username-query=
                "select username, password, enabled from userdetails where userName=?"
              authorities-by-username-query=
                "select ud.username as username, rm.name as role from userdetails ud INNER JOIN rolemaster rm ON rm.id = ud.roleId  where username = ?" />
          </authentication-provider>
        </authentication-manager>
    
        <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
        <beans:bean id="customLogoutSuccessHandler" class="com.pvn.mvctiles.configuration.CustomLogoutSuccessHandler" />
    
        <beans:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
             <beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
             <beans:property name="url" value="jdbc:mysql://localhost:3306/springmvc" />
             <beans:property name="username" value="root"/>
             <beans:property name="password" value="root"/>
             <beans:property name="initialSize" value="2" />
             <beans:property name="maxActive" value="5" />
        </beans:bean>       
    </beans:beans>
    

    2。在你的 Dao 中实现 UserDetailsS​​ervice 并在 Authentication manager buider 中注入 UserDetailService

    这里需要重写 loadUserByUsername 方法 1. 通过在方法中作为参数传递的用户名从数据库加载用户密码。 2.从数据库中为用户加载authorities,并构造一个GrantedAuthority列表 3. 通过传递在步骤 1 和步骤 2 中获取的密码和权限来创建用户 4. 返回UserDetail对象,让spring容器自己负责认证和授权。

    @Component
    public class UserDaoImpl implements UserDao, UserDetailsService
    {
    
        Logger          OUT = LoggerFactory.getLogger(UserDaoImpl.class);
    
        @Autowired
        SessionFactory  sessionFactory;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
        {
            try (Session session = sessionFactory.openSession();)
            {
    
                CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
                CriteriaQuery<DbUserDetails> userCriteria = criteriaBuilder.createQuery(DbUserDetails.class);
                Root<DbUserDetails> userRoot = userCriteria.from(DbUserDetails.class);
                userCriteria.select(userRoot).where(criteriaBuilder.equal(userRoot.get("userName"), username));
    
                Query<DbUserDetails> userQuery =session.createQuery(userCriteria);
                DbUserDetails dbUser = userQuery.getSingleResult();
    
                CriteriaQuery<RoleMaster> roleCriteria = criteriaBuilder.createQuery(RoleMaster.class);
                Root<RoleMaster> roleRoot = roleCriteria.from(RoleMaster.class);
                roleCriteria.select(roleRoot).where(criteriaBuilder.equal(roleRoot.get("id"), dbUser.getRoleId()));
    
                Query<RoleMaster> roleQuery =session.createQuery(roleCriteria);
                RoleMaster role = roleQuery.getSingleResult();
    
                List<GrantedAuthority> authList = new ArrayList<>();
                authList.add(new SimpleGrantedAuthority(role.getName()));
    
                return new User(username, dbUser.getPassword(),true, true, true, true, authList);
            }
            catch (Exception e)
            {
                OUT.error("Exception - {}", e);
                throw new UsernameNotFoundException("Exception caught", e);
            }
        }
    }
    
    @Configuration
    @EnableWebSecurity
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter 
    {   
        @Autowired
        UserDaoImpl userDaoImpl;
    
        @Autowired
        public void configureUserDetailsService(AuthenticationManagerBuilder auth) throws Exception
        {
            auth.userDetailsService(userDaoImpl);
        }
        ...
    }
    

    3。通过注册自定义身份验证提供程序。

    在上一步(实现 UserDetailsS​​ervice)中,loadUserByUsername 方法仅通过用户名争论(无法获取密码)传递,但是在与第三方系统进行身份验证时,它不会向您提供存储的凭据,而是会要求您提供证书。因此,您需要用户名和密码。您可以通过实现 AuthenticationProvider 来获得它,如下所示。一旦针对第三方系统的身份验证成功,您只需要设置一个令牌(UsernamePasswordAuthenticationToken)并将其返回给spring security。

    @Configuration
    @EnableWebSecurity
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private CustomAuthenticationProvider authProvider;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(authProvider);
        }
    
        ...
    }
    
    @Component
    public class CustomAuthenticationProvider implements AuthenticationProvider 
    {
    
        @Override
        public Authentication authenticate(Authentication authentication) 
          throws AuthenticationException {
    
            String name = authentication.getName();
            String password = authentication.getCredentials().toString();
    
            //Do your authentication
            // use the credentials
            // and authenticate against the third-party system
            if (authenticatedSuccessfully) 
            {
                return new UsernamePasswordAuthenticationToken(
                  name, password, new ArrayList<>());
            } 
            else 
            {
                return null;
            }
        }
    
        @Override
        public boolean supports(Class<?> authentication) 
        {
            return authentication.equals(UsernamePasswordAuthenticationToken.class);
        }
    }
    

    【讨论】:

      【解决方案2】:

      你应该试试auth.authenticationProvider(new YourAuthenticationProvider()),而不是auth.inMemoryAuthentication()。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-10-27
        • 1970-01-01
        • 2014-11-18
        • 2011-01-31
        • 2023-02-05
        • 2011-09-16
        • 2011-05-16
        • 2022-06-13
        相关资源
        最近更新 更多