Spring Security快速上手(含代码)

522 阅读5分钟

1、Spring Security介绍

Spring 是一个非常流行和成功的java应用开发框架。

Spring Security 基于Spring 框架,提供了一套web应用安全性的完整解决方案。

Web 应用的安全性包括两部分:

用户认证(Authentication)

用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。

用户授权(Authorization)

用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。

系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

2、流程说明

1.png

3、核心组件

2.png

3.1 引入security依赖

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>5.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.2.4.RELEASE</version>
        </dependency>

3.2 Spring容器配置

/**
 * 该配置文件相当于applicationContext.xml文件
 */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "cn.codewei",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class ApplicationConfig {
    // 在此配置除了Controller的其他bean,比如数据库连接池,事务管理器,业务bean等
}

3.3 ServletContext配置

/**
 * 该配置,就相当于Springmvc.xml文件
 */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "cn.codewei",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    // 视图解析器
    @Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
        internalResourceViewResolver.setPrefix("/WEB-INF/view/");
        internalResourceViewResolver.setSuffix(".jsp");
        return internalResourceViewResolver;
    }
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
    }
}

3.4 加载Spring容器

在init包下定义Spring容器初始化类SpringApplicationInitializer,此类实现WebApplicationInitializer接口,Spring容器启动时加载WebApplicationInitializer接口的所有实现类。

/**
 * 加载Spring容器
 */
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    // spring容器,相当于加载applicationContext.xml
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ApplicationConfig.class};
    }
    // servleContext,相当于加载springmvc.xml
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }
    // url-mapping
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

4、认证

4.1 认证页面

springSecurity默认提供认证页面,不需要额外开发。

image-20200727133113296

4.2 安全配置

spring security提供了用户名密码登录、退出、会话管理等认证功能,只需要配置即可使用。

1.在config包下定义WebSecurityConfig,安全配置的内容包括:用户信息、密码编码器、安全拦截机制。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    // 定义用户信息服务(查询用户信息)
    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
        return manager;
    }
    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();  //NoOpPasswordEncoder 不采用加密算法
    }
    // 配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/r/**").authenticated()  // 所有/r/** 的请求必须认证通过
                .anyRequest().permitAll()  // 其他请求,可以不认证进行访问
                .and()
                .formLogin()  // 允许表单登录
                .successForwardUrl("/login-success");  // 自定义登录成功的页面地址
    }
}

在 **userDetailsService()**方法中,我们返回了一个UserDetailsService给spring容器,Spring Security会使用它来获取用户信息。我们暂时使用InMemoryUserDetailsManager实现类,并在其中分别创建了zhangsan、lisi两个用户,并设置密码和权限。

而在**configure()**中,我们通过HttpSecurity设置了安全拦截规则,其中包含了以下内容:

(1)url匹配/r/**的资源,经过认证后才能访问。

(2)其他url完全开放。

(3)支持form表单认证,认证成功后转向/login-success。

2.加载 WebSecurityConfig

修改SpringApplicationInitializer的getRootConfigClasses()方法,添加WebSecurityConfig.class:

/**
 * 加载Spring容器
 */
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    // spring容器,相当于加载applicationContext.xml
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ApplicationConfig.class, WebSecurityConfig.class};
    }
    // servleContext,相当于加载springmvc.xml
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }
    // url-mapping
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

4.3 Spring Security初始化

Spring Security 初始化,这里有两种情况

  • 若当前环境没有使用 Spring或Spring MVC,则需要将 WebSecurityConfig(Spring Security配置类) 传入超类,以确保获取配置,并创建spring context。
  • 相反,若当前环境已经使用 spring,我们应该在现有的springContext中注册Spring Security(上一步已经做将WebSecurityConfig加载至rootcontext),此方法可以什么都不做。

在init包下定义SpringSecurityApplicationInitializer:

public class SpringSecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
    public SpringSecurityApplicationInitializer(){
//        super(WebSecurityConfig.class);
    }
}

4.4 默认根路径请求

在WebConfig.java中添加默认请求根路径跳转到/login,此url为spring security提供:

public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("redirect:/login");
}

spring security默认提供的登录页面。

4.5 认证成功页面

在安全配置中,认证成功将跳转到/login-success,代码如下:

// 配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/r/**").authenticated()  // 所有/r/** 的请求必须认证通过
        .anyRequest().permitAll()  // 其他请求,可以不认证进行访问
        .and()
        .formLogin()  // 允许表单登录
        .successForwardUrl("/login-success");  // 自定义登录成功的页面地址
}

spring security支持form表单认证,认证成功后转向/login-success。

在LoginController中定义/login-success:

@RestController
public class LoginController {
    @RequestMapping(value = "/login-success",produces = {"text/html;charset=utf-8"})
    public String loginSuccess(){
        return "登录成功";
    }
}

5、测试

(1) 启动项目,访问http://localhost:8080/路径地址

img

页面会根据WebConfig中addViewControllers配置规则,跳转至/login,/login是pring Security提供的登录页面。

(2) 登录

输入错误的用户名、密码

image-20200727133208549

输入正确的用户名、密码,登录成功

image-20200727133227366

(3) 退出

请求/logout退出

image-20200727133623809

image-20200727133655599

6、授权

实现授权需要对用户的访问进行拦截校验,校验用户的权限是否可以操作指定的资源,Spring Security默认提供授权实现方法。

在LoginController添加/r/r1或/r/r2

/**
* 测试资源
* @return
*/
@GetMapping(value = "/r/r1",produces = "text/plain;charset=utf-8")
public String r1(HttpServletRequest request){
    return "访问资源r1";
}
/**
* 测试资源
* @return
*/
@GetMapping(value = "/r/r2",produces = "text/plain;charset=utf-8")
public String r2(HttpServletRequest request){
    return "访问资源r2";
}

在安全配置类 WebSecurityConfig.java中配置授权规则

.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")

.antMatchers(“/r/r1”).hasAuthority(“p1”)表示:访问/r/r1资源的 url需要拥有p1权限

.antMatchers(“/r/r2”).hasAuthority(“p2”)表示:访问/r/r2资源的 url需要拥有p2权限

完整的WebSecurityConfig方法如下:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    // 定义用户信息服务(查询用户信息)
    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
        return manager;
    }
    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();  //NoOpPasswordEncoder 不采用加密算法
    }
    // 配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/r/r1").hasAuthority("p1")
                .antMatchers("/r/r2").hasAuthority("p2")
                .antMatchers("/r/**").authenticated()  // 所有/r/** 的请求必须认证通过
                .anyRequest().permitAll()  // 其他请求,可以不认证进行访问
                .and()
                .formLogin()  // 允许表单登录
                .successForwardUrl("/login-success");  // 自定义登录成功的页面地址
    }
}