Spring Security快速上手

Spring Security快速上手

Spring Security介绍

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.

创建工程

在security项目的基础上增加spring-security依赖:

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

    <dependency>
         <groupId>org.springframework.security</groupId>
         <artifactId>spring-security-config</artifactId>
         <version>5.3.2.RELEASE</version>
     </dependency>
Spring容器配置

同security项目一样:

package com.cehcloud.cehc.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

/**
 * @author Lenovo
 * @date 2020/8/18 21:25
 */
@Configuration
@ComponentScan(basePackages = "com.cehcloud.cehc"
                ,excludeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
public class ApplicationConfig {
    
    
    // 在此配置除了Controller的其它bean,比如:数据库、事务管理器、业务bean等
}
Servlet Context配置

同security项目一样:

package com.cehcloud.cehc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * @author Lenovo
 * @date 2020/8/18 21:29
 */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.cehcloud.cehc"
                ,includeFilters = {
    
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    
    

    @Bean
    public InternalResourceViewResolver viewResolver() {
    
    
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/view/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;

    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
      registry.addViewController("/").setViewName("login");
    }
}
加载Spring容器

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

package com.cehcloud.cehc.init;

import com.cehcloud.cehc.config.ApplicationConfig;
import com.cehcloud.cehc.config.WebConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * @author Lenovo
 * @date 2020/8/18 21:45
 */
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    

    /**
     * Spring容器,相当于加载applicationContext.xml
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
    
    
        return new Class[]{
    
    ApplicationConfig.class};
    }

    /**
     * servletContext,相当于加载springmvc.xml
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
    
    
        return new Class[]{
    
    WebConfig.class};
    }

    /**
     * url-mapping
     * @return
     */
    @Override
    protected String[] getServletMappings() {
    
    
        return new String[]{
    
    "/"};
    }
}

认证

认证页面

spring-security默认提供了认证页面,不需要额外开发
图片五

安全配置

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

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

package com.cehcloud.cehc.config;

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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import javax.servlet.http.HttpSession;

/**
 * @author Lenovo
 * @date 2020/8/19 15:49
 */
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    /**
     * 定义用户信息服务
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
    
    
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("v1").build());
        manager.createUser(User.withUsername("lisi").password("12345").authorities("v2").build());
        return manager;
    }

    /**
     * 密码编辑器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 配置安全拦截机制
     * @param httpSecurity
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    
    
        httpSecurity
                    .authorizeRequests()
                    .antMatchers("/t/**").authenticated()
                    .anyRequest().permitAll()
                    .and()
                    .formLogin()
                    .successForwardUrl("/login-success");
    }
}

(2)加载WebSecurityConfig

 /**
     * Spring容器,相当于加载applicationContext.xml
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
    
    
        return new Class[]{
    
    ApplicationConfig.class, WebSecurityConfig.class};
    }
Spring Security初始化

spring security初始化有两种情况:

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

在init包下定义SpringSecurityApplicationInitializer:

package com.cehcloud.cehc.init;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

/**
 * @author Lenovo
 * @date 2020/8/19 16:05
 */
public class SpringSecurityApplicationInitializer 
        extends AbstractSecurityWebApplicationInitializer {
    
    

    public SpringSecurityApplicationInitializer(){
    
    
        
    }
}
默认根路径请求

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

	/**
     * 默认url根路径跳转到/login吃url为spring security提供
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
      registry.addViewController("/").setViewName("redirect:login");
    }
认证成功的页面

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

	/**
     * 配置安全拦截机制
     * @param httpSecurity
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    
    
        httpSecurity
                    .authorizeRequests()
                    .antMatchers("/t/**").authenticated()
                    .anyRequest().permitAll()
                    .and()
                    .formLogin()
                    .successForwardUrl("/login-success");
    }

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

在LoginController中定义/login-success:

	/**
     * 登录成功页面
     * @return
     */
    @RequestMapping(value = "/login-success", produces = "text/plain;charset=utf-8")
    public String loginSuccess() {
    
    
        return "登录成功";
    }
授权

在LoginController中添加/t/t1或/t/t2

package com.cehcloud.cehc.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Lenovo
 * @date 2020/8/19 16:16
 */
@RestController
public class LoginController {
    
    

    /**
     * 登录成功页面
     * @return
     */
    @RequestMapping(value = "/success", produces = {
    
    "text/plain;charset=UTF-8"})
    public String loginSuccess() {
    
    
        return "登录成功";
    }

    /**
     * 访问资源1
     * @return
     */
    @GetMapping(value = "/t/t1", produces = {
    
    "text/plain;charset=UTF-8"})
    public String r1() {
    
    
        return "访问资源1";
    }

    /**
     * 访问资源2
     * @return
     */
    @GetMapping(value = "/t/t2", produces = {
    
    "text/plain;charset=UTF-8"})
    public String r2() {
    
    
        return "访问资源1";
    }
}

spring Security 应用详解

集成SpringBoot

spring容器配置

SpringBoot工程启动会自动扫描启动类所在包下的所有Bean,加载到spring容器.

(1)Spring配置文件

server:
  port: 8080
  servlet:
    context-path: /spring-boot-security
spring:
  application:
    name: spring-boot-security

(2)Spring Boot启动类

package com.cehcloud.cehc.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SecurityApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(SecurityApplication.class, args);
    }

}
Servlet Context配置

由于Spring Boot starter自动装配机制,这里无需使用@EnableWebMvc与@ComponenetScan,WebConfig如下:

package com.cehcloud.cehc.security.config;

import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Lenovo
 * @date 2020/8/20 13:07
 */
public class WebConfig implements WebMvcConfigurer {
    
    

    /**
     * 默认URL根路径跳转到/login此url为spring security提供
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("redirect:/login");
    }
}

视图解析器配置在全局配置文件中:

server:
  port: 8080
  servlet:
    context-path: /spring-boot-security
spring:
  application:
    name: spring-boot-security
  mvc:
    view:
      prefix: /templates/
      suffix: .jsp
安全配置

由于SpringBoot的自动装配机制,这里无需要@EnableWebSecurity注解,WebSecurityConfig内容如下:

package com.cehcloud.cehc.security.config;

import org.springframework.context.annotation.Configuration;
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.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @author Lenovo
 * @date 2020/8/19 15:49
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    /**
     * 定义用户信息服务
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
    
    
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("123").password("123").authorities("v1").build());
        manager.createUser(User.withUsername("123456").password("123456").authorities("v2").build());
        return manager;
    }

    /**
     * 密码编辑器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 配置安全拦截机制
     * @param httpSecurity
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    
    
        httpSecurity.authorizeRequests()
                    .antMatchers("/t/**").authenticated()
                    .anyRequest().permitAll()
                    .and()
                    .formLogin()
                    .successForwardUrl("/success");
    }
}

工作原理

认证流程

图片六

AuthenticationProvider

AuthenticationProvider是一个接口,定义如下:

public interface AuthenticationProvider {
    
    
    Authentication authenticate(Authentication authenticatiion) throws AuthenticationException;
    boolean supports(Class<?> var1);
}

​ authenticate()方法定义了认证的实现过程,它的参数是一个Authentication,里面包含了登录用户的用户名、密码。而返回值也是一个Authentication,这个Authentication则是在认证成功后,将用户的权限及其它的信息重新组装后生成。

​ Spring Security中维护着一个List列表,存放多种认证方式,不同的认证方式使用不同的AuthenticationProvider.

​ 每个AuthenticationProvider需要实现supports()方法来表名自己支持的认证方式,如我们使用表单方式认证,在提交请求时Spring Security会生成UsernamePasswordAuthenticationToken,它是一个Authentication,里面封装着用户提交的用户名、密码等信息,而对象的,哪个AuthenticationProvider来处理它。

我们在DaoAuthenticationProvider的基类AbstractUserDetailsAuthenticationProvider发现以下代码:

public boolean supports(Class<?> authentication) {
    
    
    return UsernamePasswordAuthenticationToken.class.isAssignableForm(authentication);
}

也就是说当web表单提交用户名密码时,Spring Security由DaoAuthenticationProvider处理.

最后,我们来看一下Authentication(认证信息)的结构,它是一个接口,我们之前提到的UsernamePasswordAuthenticationToken就是它的实现之一.

public interface Authentication extends Principal, Serializable {
    
    
    Collection<? extends GrantedAuthority> getAuthorities();
    
    Object getCredentials();
    
    Object getDetails();
    
    Object getPrincipal();
    
    boolean isAuthenticated();
    
    void setAuthenticated(boolean varl) throws IllegalArgumentException;
}
UserDetailsService

现在我们知道DaoAuthenticationProvider处理了Web表单的认证逻辑,认证成功后即得到一个Authentication(UsernamePasswordAuthenticationToken实现),里面包含了身份信息,这个身份信息就是一个Object,大多数情况下被强制转为UserDetials对象.

DaoAuthenticationProvider中包含了UserDetialsService实例,他负责根据用户名提取用户信息,而后DaoAuthenticationProvider会去对比UserDetailsService提取的用户密码与用户提交的密码时候匹配作为认证的关键依据.

public interface UserDetailsService {
    
    
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

Spring Security 提供的InMemoryUserDetailsManager(内存认证),JdbcUserDetailsManager(jdbc认证)就是UserDetailsService的实现类,主要区别无非是从内存还是从数据库加载用户.

自定义认证

导入依赖

因为SPringBoot不提倡用jsp所以在使用的时候需要导入以下依赖:

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
</dependency>
配置认证页面

在WebConfig中配置认证页面地址

package com.cehcloud.cehc.security.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Lenovo
 * @date 2020/8/20 13:07
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    

    /**
     * 默认URL根路径跳转到/login此url为spring security提供
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("redirect:/login-view");
        registry.addViewController("/login-view").setViewName("login");
    }
}
安全配置

在WebSecurityConfig中配置表单登录信息

 	/**
     * 配置安全拦截机制
     * @param httpSecurity
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    
    
        httpSecurity    .csrf().disable()
                        .authorizeRequests()
                        .antMatchers("/t/**").authenticated()
                        .anyRequest().permitAll()
                        .and()
                        .formLogin()
                            .loginPage("/login-view")
                            .loginProcessingUrl("/login")
                            .successForwardUrl("/success")
                            .permitAll();

    }
问题解决

spring Security为了防止CSRF的发生,限制了除了get以外的大多数方法.

解决方法一

屏蔽CSRF控制,即spring Security不再限制CSRF.

配置WebSecurityConfig

 @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    
    
        httpSecurity    .csrf().disable()
            ...
    }
解决方法二

在登录页面添加一个token,spring security会验证token,如果token合法则可以继续请求.

修改login.jsp

<%--
  Created by IntelliJ IDEA.
  User: Lenovo
  Date: 2020/8/19
  Time: 13:53
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
<form action="login" method="post">
    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
    用户名:<input type = "text" name = "username"/><br>
    密码:<input type="password" name="password"/><br>
    <input type="submit" value="登录">
</form>
</body>
</html>
连接数据库认证
创建数据库

创建user_db数据库

CREATE DATABASE `user_db`CHARACTER SET utf8 COLLATE utf8_general_ci;

创建t_user表

CREATE TABLE `t_user`( 
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户id', 
    `username` VARCHAR(64) NOT NULL, 
    `password` VARCHAR(64) NOT NULL, 
    `fullname` VARCHAR(255) NOT NULL COMMENT '用户姓名', 
    `mobile` VARCHAR(11) NOT NULL COMMENT '手机号', 
    PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8; 
代码实现

添加数据源

server:
  port: 8080
  servlet:
    context-path: /spring-boot-security
spring:
  application:
    name: spring-boot-security
  mvc:
    view:
      prefix: /WEB-INF/view/
      suffix: .jsp
  datasource:
    url: mysql://localhost:3306/user_db
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

添加依赖:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

定义Dao

在定义模型类型,在model包定义UserDTO:

package com.cehcloud.cehc.security.model;

import lombok.Data;

/**
 * @author Lenovo
 * @date 2020/8/20 15:52
 */
@Data
public class UserDTO {
    
    

    /**
     * 用户ID
     */
    private String id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 用户密码
     */
    private String password;
    /**
     * 用户姓名
     */
    private String fullname;
    /**
     * 用户电话
     */
    private String mobile;
}

在Dao包定义UserDAO

package com.cehcloud.cehc.security.dao;

import com.cehcloud.cehc.security.model.UserDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author Lenovo
 * @date 2020/8/20 15:57
 */
@Repository
public class UserDAO {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public UserDTO getUserByUsername(String username) {
    
    

        String sql = "select id, username, password, fullname, mobile from t_user where username = ?";
        List<UserDTO> list = jdbcTemplate.query(sql, new Object[]{
    
    username}, new BeanPropertyRowMapper<>(UserDTO.class));
        if (list == null && list.size() == 1) {
    
    
            return null;
        }
        return list.get(0);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_44880095/article/details/112906906