美文网首页
Spring Security 4

Spring Security 4

作者: simonzeng | 来源:发表于2017-09-30 14:35 被阅读0次

    作者

    yoyoyosiyu@126.com

    时间

    2017年9月

    简述

    这段时间一直在不断的尝试配置和使用Spring Security, 取得了一些成果,也大概的理解了一些其中的原理,担心日后忘记,特此记录一下,作为备忘。
    Spring 的配置分为XML和Java两种,还有一种是混合。我用的是混合。至于组件管理,我用的是Gradle。Maven其实也差不多。

    环境

    • Intellij Idea
    • gradle
    • java 1.8
    • tomcat 9

    首先是一些依赖的组件
    build.gradle

    group 'com.kgl1688'
    version '1.0-SNAPSHOT'
    
    apply plugin: 'java'
    apply plugin: 'war'
    
    sourceCompatibility = 1.8
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        compile 'org.springframework:spring-webmvc:4.2.3.RELEASE'
        compile 'org.springframework.security:spring-security-web:4.2.3.RELEASE'
        compile 'org.springframework.security:spring-security-config:4.2.3.RELEASE'
    
        providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.0'
    
        testCompile group: 'junit', name: 'junit', version: '4.11'
        testCompile group: 'junit', name: 'junit', version: '4.12'
    }
    

    先配置Spring MVC

    Spring MVC 的Java配置

    src/com/kgl1688/config/WebApplicationInitializer.java

    package com.kgl1688.config;
    
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[] {RootConfigu.class};
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[] {WebConfig.class};
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[] {"/*"};
        }
    }
    

    src/com/kgl1688/config/WebConfig.java

    package com.kgl1688.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.servlet.view.InternalResourceViewResolver;
    
    @Configuration
    @EnableWebMvc
    @ComponentScan("com.kgl1688.mvc")
    public class WebConfig extends WebMvcConfigurerAdapter {
    
        @Bean
        public ViewResolver viewResolver() {
            InternalResourceViewResolver internalResourceViewResolver =
                    new InternalResourceViewResolver();
    
            internalResourceViewResolver.setPrefix("/WEB-INF/views/");
            internalResourceViewResolver.setSuffix(".jsp");
    
            return internalResourceViewResolver;
        }
    }
    

    src/com/kgl1688/config/RootConfig.java

    package com.kgl1688.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.ImportResource;
    
    @Configuration
    @ComponentScan
    public class RootConfig {
    }
    

    有了这三个类,一个Spring MVC的环境就配置完成了。其中几个要点如下

    • WebApplicationInitializer
      这个是Spring MVC 环境的启动点,之所以他是启动点,因为他继承自AbstractAnnotationConfigDispatcherServletInitializer。更细节的原因可以度娘。

    • @EnableWebMvc
      这个注解是启动Spring MVC 的关键

    • @ComponentScan
      这个注解会启动组件扫描,指示Spring 扫描使用了该注解的类所在的包及其下的所有子包的组件(RootConfiguration 在 com.kgl1688.config 包,那么com.kgl1688.config 以及其下的包都会被扫描)

    • @ImportResource
      这个注解可以引入XML类型的Beans的定义,这也是XML和Java混合配置的连接点

    Spring Security 的Java配置

    添加一个文件在src/com/kgl1688/config/ 目录下:
    src/com/kgl1688/config/SecurityWebApplicationInitializer.java

    package com.kgl1688.config;
    
    import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
    
    public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer  {
    }
    

    不需要实现和重载任何方法,有就可以了,并且在什么包下都没有关系。其同样会被Servlet 3.0 的容器探测到。运行测试,在后台的LOG里会显示如下的信息:

    Paste_Image.png

    这时候的Spring Security 环境的初始化已经被引入,但还缺少需要的关键组件。这时候有两种选择:Java 配置 或者 XML配置文件

    JAVA类配置

    添加 SecurityConfig.java 到 src/com.kgl1688.config目录下

    package com.kgl1688.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.access.AccessDecisionManager;
    import org.springframework.security.config.annotation.ObjectPostProcessor;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
    import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("user").password("password").roles("USER");
        }
    }
    

    关键点:

    • 必须在com.kgl1688.config 包下,因为之前我们配置Spring MVC 环境的时候@ComponentScan指示在这个包里自动扫描组件
    • @EnableWebSecurity, 和 Spring MVC 一样,这个注解会启用Spring Security一些关键的组件。

    自此,Spring Security的环境就配置好了。

    XML 配置

    首先在RootConfig.java文件的类定义中增加

    @ImportResource("classpath:security.xml")

    package com.kgl1688.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.ImportResource;
    
    @Configuration
    @ImportResource("classpath:security.xml")
    @ComponentScan
    public class RootConfig {
    }
    

    然后添加security.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <b:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:b="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 />
    
        <user-service>
            <user name="user" password="password" authorities="ROLE_USER" />
        </user-service>
    
    </b:beans>
    

    这样就有了最小的Spring Security环境和一些默认的配置。

    在默认的情况下,访问除了/login, /logout 这些地址之外都需要认证,在没有认证通过之前都会跳到登录页面: /login。因为我们定义了一个用户user, 密码为password,我们可以用这个账户登录。我们可以通过访问/logout来退出登录。这些都是Spring Security 给我们提供的,包括登录页面。

    修改默认的规则

    如果我们只是希望那些我们希望保护的请求地址要求验证,其他的可以自由访问。那么我们需要修改默认的规则来达到我们的要求
    现在我们希望 /secure下的所有请求地址都要求认证,那么我们可以配置如下:

    • JAVA配置

    更改src/com.kgl1688.config/SecurityConfig

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //super.configure(http);
            http
                    .authorizeRequests()
                        .antMatchers("/secure/**")
                        .authenticated()
                        .and()
                    .formLogin()
                        .and()
                    .httpBasic();
        }
    
    
    • 等效的XML 配置
    <?xml version="1.0" encoding="UTF-8"?>
    <b:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:b="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>
            <intercept-url pattern="/secure/**" access="authenticated" />
            <form-login />
            <http-basic />
        </http>
    
        <user-service>
            <user name="user" password="password" authorities="ROLE_USER" />
        </user-service>
    
    </b:beans>
    

    现在访问 /secure 之下的地址都需要认证,认证方法支持 FormLogin(表单登录)和 HttpBasic (API常用的方式,无界面,通过请求头携带认证信息)两种方式。我们可以通过浏览器访问 /Secure 下的地址,如果未登录的话,会跳转到登录页面。我们也可以通过 postman这样的API测试工具测试使用HttpBasic方式。

    为不同的请求地址指定不同的访问角色

    假设你希望所有的人可以访问 /secure, 但是只有管理员能访问 /secure/admin

    • JAVA 配置
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("user").password("password").roles("USER").and()
                    .withUser("admin").password("password").roles("ADMIN");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                        .antMatchers("/secure/admin/**")
                        .hasRole("ADMIN")
                        .and()
                    .authorizeRequests()
                        .antMatchers("/secure/**")
                        .authenticated()
                        .and()
                    .formLogin()
                        .and()
                    .httpBasic();
        }
    
    • XML配置
        <http>
            <intercept-url pattern="/secure/admin/**" access="hasRole('ADMIN')" />
            <intercept-url pattern="/secure/**" access="authenticated" />
            <form-login />
            <http-basic />
            <logout />
        </http>
    
        <user-service>
            <user name="user" password="password" authorities="ROLE_USER" />
            <user name="admin" password="password" authorities="ROLE_ADMIN" />
        </user-service>
    

    这里唯一需要注意的是 intercept-url定义的顺序。Spring Security的规则是如果命中 pattern 中的条件,将会应用此条规则,后续的其他的 intercept-url都会忽略。

    我们增加了一个新的用户 admin,并且让其具有ADMIN权限。

    为不同的请求提供不同的认证方式

    如果我们希望不同的请求地址使用不同的认证方式,比如 /api/** 这样的地址一般是作为api调用,而不是给浏览器页面显示给用户的,我们希望采用HttpBasic认证方式,而其他仍然保留 FormLogin 的方式。
    intercept-url 似乎不支持在其中加入 http-basic, intercept-urlhttp-basic是同一级别的,都在 http之下。
    但是可以有多个http, 所以我们可以在不同的http下指定不同的认证方式。

    • JAVA 配置
    package com.kgl1688.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.provisioning.InMemoryUserDetailsManager;
    
    @EnableWebSecurity
    public class SecurityConfig {
    
    
        @Bean
        public UserDetailsService userDetailsService() throws Exception {
            InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            manager.createUser(User.withUsername("user").password("password").roles("USER").build());
            manager.createUser(User.withUsername("admin").password("password").roles("ADMIN").build());
            return manager;
        }
    
        @Configuration
        @Order(1)
        public class ApiWebSecurityConfigurerAdapter extends  WebSecurityConfigurerAdapter {
    
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http
                        .antMatcher("/api/**")
                        .authorizeRequests()
                            .anyRequest()
                            .authenticated()
                            .and()
                        .httpBasic();
            }
        }
    
        @Configuration
        public class FormLoginWebSecurityConfigurerAdapter extends  WebSecurityConfigurerAdapter {
    
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http
                        .authorizeRequests()
                            .antMatchers("/secure/admin/**")
                            .hasRole("ADMIN")
                            .and()
                        .authorizeRequests()
                            .antMatchers("/secure/**")
                            .authenticated()
                            .and()
                        .formLogin()
                            .and()
                        .httpBasic();
            }
        }
    }
    

    这里必须要注意的是antMatcher("/api/**")的写法,他写在authorizeRequests()之前,这和写在authorizeRequests()之后有不同的含义,写在前面是设置http对象的,而写在authorizeRequests之后是设置authorizeRequests调用后返回的ExpressionInterceptUrlRegistry对象的。如果观察下面与之等效的XML配置,它实际上相当与 <http> 的 pattern 属性。

    在XML配置中,<http>有出现的前后次序,可是在JAVA代码中,反射机制并不会因为类在代码中写的位置的前后而有所区别,所以必须依赖于 @Order注解。没有@order注解的相当于优先级在最后。

    另外一个要补充的知识点是JAVA代码中配置的写法规则。http对象 和 XML配置中的 <http> 类似。而 authorizeRequests 相当于 <http>中的 <intercept-url>, formLogin 等价于 <form-login>,httpBasic 等价于 <http-basic>。 而 and() 就相当于闭合一个元素,比如 .formLogin().and()就等于<form-login></form-login>。因此 http.antMatcher("/api/**") 就等价于 <http pattern="/api/**">

    • XML配置
    <?xml version="1.0" encoding="UTF-8"?>
    <b:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:b="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 pattern="/api/**">
            <intercept-url pattern="/api/**" access="authenticated" />
            <http-basic />
        </http>
    
        <http>
            <intercept-url pattern="/secure/admin/**" access="hasRole('ADMIN')" />
            <intercept-url pattern="/secure/**" access="authenticated" />
            <form-login />
            <http-basic />
            <logout />
        </http>
    
        <user-service>
            <user name="user" password="password" authorities="ROLE_USER" />
            <user name="admin" password="password" authorities="ROLE_ADMIN" />
        </user-service>
    
    </b:beans>
    

    相关文章

      网友评论

          本文标题:Spring Security 4

          本文链接:https://www.haomeiwen.com/subject/qxcvextx.html