SpringSecurity

本文介绍了SpringSecurity的安全框架,包括其核心过滤器、快速入门步骤、内存和数据库的用户认证与授权配置,以及加密方式。通过实例展示了如何创建自定义认证页面,配置多用户,并进行URL和方法级别的授权。
摘要由CSDN通过智能技术生成

目录

介绍

常用的15个过滤器

快速开始

1.创建一个springboot程序,导入相关的坐标

2.启动程序

3.修改默认的用户名和密码

配置自定义认证页面

基于内存的多用户认证

基于内存的用户授权

加密方式

基于数据库的多用户认证与授权

认证

1.创建数据库模型

2.导入需要的依赖坐标

3.创建对应的实体类

4.创建自定义的用户详情对象,实现UserDetails

5.编写mapper接口

6.编写认证相关的业务逻辑类

授权

1.新建认证相关的实体类

2.修改自定义的UserDetails添加上权限功能

3.编写查询权限的mapper接口

4.修改认证相关的业务逻辑层

5.针对方法或url授权


介绍

Spring Security是基于Spring的安全框架,它提供了一种声明式的安全访问控制解决方案。它提供了包括身份验证、授权、运行时访问控制、单点登录、注销等功能。

Spring Security提供了许多集成选项,包括集成WebMVC、REST、SOAP等应用程序,也支持和Spring Boot等常见框架进行集成。

Spring Security的核心原则是基于过滤器链(FilterChain)来实施安全策略。Spring Security内部维护了一个过滤器链,每个过滤器都可以执行一些特定的安全任务。过滤器链按顺序执行,每当一个请求进入应用程序时,过滤器链就会进行筛选和处理。

在Spring Security中,安全是基于角色和权限的。角色是一组权限的集合,而权限是一种特定的访问控制。

最后,Spring Security具有高度的可配置性和可扩展性,可以定制各种安全策略,并支持开发自定义的安全插件。

常用的15个过滤器

  1. UsernamePasswordAuthenticationFilter:该过滤器处理基于用户名和密码的身份验证。

  2. BasicAuthenticationFilter:该过滤器处理基于HTTP基本身份验证的身份验证。

  3. AnonymousAuthenticationFilter:该过滤器允许未经身份验证的用户访问应用程序。

  4. RememberMeAuthenticationFilter:该过滤器为具有"记住我"功能的身份验证提供支持。

  5. ExceptionTranslationFilter:该过滤器负责处理Spring Security中发生的异常。

  6. FilterSecurityInterceptor:该过滤器基于数据库中的安全配置保护应用程序资源。

  7. SessionManagementFilter:该过滤器管理用户会话,并处理会话超时、并发登录等问题。

  8. CsrfFilter:该过滤器提供跨站点请求伪造保护。

  9. LogoutFilter:该过滤器处理用户注销并管理用户会话。

  10. SwitchUserFilter:该过滤器允许管理员模拟其他用户进行操作。

  11. RequestCacheAwareFilter:该过滤器允许将未经身份验证的请求缓存,以便在身份验证后重新处理这些请求。

  12. ConcurrentSessionFilter:该过滤器处理并发登录的问题。

  13. SessionFixationProtectionFilter:该过滤器提供会话固定保护。

  14. SecurityContextHolderAwareRequestFilter:该过滤器将SecurityContext添加到HttpServletRequest的常规属性中。

  15. OAuth2ClientAuthenticationProcessingFilter:该过滤器提供OAuth2客户端身份验证的支持。

快速开始

1.创建一个springboot程序,导入相关的坐标

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2.启动程序

这个时候访问该应用的资源就会自动跳转到认证页面,用户名是user,密码是控制台上打印的一串字符串。

 其中177c91ab-eac6-43a4-a4d4-aef782114ce6就是密码。

这个用户是在SpringSecurity中没有配置该用户的时候自动创建的,具体源码如下:

public static class User {

		/**
		 * Default user name.
		 */
		private String name = "user";

		/**
		 * Password for the default user name.
		 */
		private String password = UUID.randomUUID().toString();

		/**
		 * Granted roles for the default user name.
		 */
		private List<String> roles = new ArrayList<>();

		private boolean passwordGenerated = true;

		public String getName() {
			return this.name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getPassword() {
			return this.password;
		}

		public void setPassword(String password) {
			if (!StringUtils.hasLength(password)) {
				return;
			}
			this.passwordGenerated = false;
			this.password = password;
		}

		public List<String> getRoles() {
			return this.roles;
		}

		public void setRoles(List<String> roles) {
			this.roles = new ArrayList<>(roles);
		}

		public boolean isPasswordGenerated() {
			return this.passwordGenerated;
		}

	}

由上面的代码可以看出,默认的用户名是user,默认的密码是随机的UUID。

3.修改默认的用户名和密码

可以通过修改配置文件来修改默认的用户名和密码。

spring:
  security:
    user:
      name: xinghe
      password: 123456

这样也会存在一个问题,就是一个系统之中只能有一个用户,这个问题在后面的内容中会有解决。

配置自定义认证页面

在一些系统中,往往不会使用SpringSecurity默认的认证页面,而是使用用户自定义的认证页面,可以通过配置SpringSecurity来设置默认的认证页面。

package com.xinghe.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;

/**
 * @author heimi
 * @version 1.0
 * @description springSecurity配置类
 * @date 2023/5/23 上午9:45
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();  // 关闭csrf
        http.authorizeRequests()
                .mvcMatchers("/login.html").permitAll()  // 释放认证页面
                .anyRequest().authenticated();  // 其它请求需要经过认证才能访问
        http.formLogin()  // 配置认证相关
                .loginPage("/login.html")  // 认证页面
                .loginProcessingUrl("/login")  // 认证处理的地址
                .failureForwardUrl("/login.html")  // 认证失败重定向地址
                .defaultSuccessUrl("/index.html");  // 认证成功默认跳转的页面
    }
}

第一条配置是关闭csrf防护,为了测试方便,最好关闭csrf防护,但是真实使用中需要开启csrf防护。

第二条配置是配置url拦截,首先匹配到/login.html,因为认证的时候需要用到login.html,所以使用permitAll来释放该页面,anyRequest是匹配除了上面配置的url之外的其它url,authenticated是需要认证才能访问。

第三条配置是配置和认证相关的,loginPage是配置认证页面,loginProcessinUUrl是处理认证请求的后端接口,failureForwardUrl是配置认证失败的重定向地址,defaultSuccessUrl是配置认证成功之后默认的跳转页面。

基于内存的多用户认证

在上面通过配置文件配置用户名和密码的时候存在一个明显的问题,就是只能配置一个用户。当然SpringSecurity可以配置多个用户,可以基于内存配置多用户,也可以基于数据库配置多用户。在SpringSecurity中的用户对象是由UserDetails的接口的实现类来封装,而创建用户详情的是UserDetailsService的实现类,如果自定义用户,那么SpringSecurity就不会自动创建用户了,因此需要自定义一个SpringSecurity相关的Bean。

package com.xinghe.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
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 heimi
 * @version 1.0
 * @description 配置用户
 * @date 2023/5/23 上午10:14
 */
@Configuration
public class SecurityUserConfig {
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails root = User.builder()
                .username("root")
                .password("123456")
                .authorities("root")
                .build();
        UserDetails admin = User.builder()
                .username("admin")
                .password("1223456")
                .authorities("admin")
                .build();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(root);
        manager.createUser(admin);
        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

在自定义多用户的时候需要配置密码编码器,所有的密码编码器都实现了PasswordEncoder接口,第二个bean就是配置密码编码器。

InMemoryUserDetailsManager是SpringSecurity对UserDetailsService默认的实现类,用户将用户创建并且存储在内存中。

第一个Bean是定义了两个User对象,然后使用InMemoryUserDetailsManager的createUser方法来创建用户,下面来看一下源码:

public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {

	private final Map<String, MutableUserDetails> users = new HashMap<>();


	public InMemoryUserDetailsManager() {
	}


	@Override
	public void createUser(UserDetails user) {
		Assert.isTrue(!userExists(user.getUsername()), "user should not exist");
		this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
	}

}

这段源码中删减掉了一些代码。可以看出在该实现类中,有一个users的map集合,通过createUser方法来创建用户详情对象,然后放进这个map集合中,这样在内存中就可以实现多用户访问了。

当然,在内存中配置多用户的时候除了使用自定义的bean配置之外还有另外一种配置方式,就是通过AuthenticationManagerBuilder来配置多用户的访问。

package com.xinghe.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @author heimi
 * @version 1.0
 * @description springSecurity配置类
 * @date 2023/5/23 上午9:45
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("root").password("123456").authorities("root");
        auth.inMemoryAuthentication()
                .withUser("admin").password("123456").authorities("admin");
    }

}

基于内存的用户授权

在上面的配置多用户的时候配置了一个authorities,就是配置用户权限的,也可以使用roles来配置角色,这两个同时配置的话只能生效一个,哪个配置比较靠后哪个生效,这两个的区别就是roles在配置的时候会在前面加上ROLE_,而authorities配置的什么就是什么,就比如说roles("user")的话,用authorities配置的话就是ROLE_user。

其中授权可以针对于url授权,也可以针对与方法授权。

针对url授权的话可以这样配置:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                        .mvcMatchers("/root").hasAnyAuthority("root")
                        .mvcMatchers("/admin").hasAnyAuthority("admin")
                        .anyRequest().authenticated();
    }

上面的配置就是说匹配到/root需要有root权限的才能访问,匹配到/admin需要有admin权限的用户才能访问,如果权限不足,就出出现403。

针对url授权的话太广泛了,可以进行更小粒度的授权,针对方法。

要向对方法进行授权,需要在SpringSecurity配置类中添加全局方法安全注解@EnableGlobalMethodSecurity

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {)

开启使用EL表达式的方法授权注解。

创建业务逻辑层并使用@PreAuthorize来控制权限:

package com.xinghe.service;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

/**
 * @author heimi
 * @version 1.0
 * @description 业务逻辑
 * @date 2023/5/23 上午11:03
 */
@Service
public class UserService {

    @PreAuthorize("hasAnyAuthority('root')")
    public String root() {
        return "root root root";
    }

    @PreAuthorize("hasAnyAuthority('admin')")
    public String admin() {
        return "admin admin admin";
    }
}

这样当访问到该方法的时候就会进行权限验证了。

加密方式

在进行密码存储的过程中不能将明文存储进去,这个时候需要用到数据加密了,SpringSecurity推荐使用BCryptPasswordEncoder加密器进行加密,如果向使用该加密器,那么直接放入IOC容器就可以了,SpringSecurity会自动找到并且使用。

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

所有的密码加密器都实现了PasswordEncoder接口。

基于数据库的多用户认证与授权

认证

1.创建数据库模型

create table user (
    id bigint primary key auto_increment,
    username varchar(50) not null ,
    password varchar(60) not null
);

create table authentication (
    id bigint primary key auto_increment,
    code varchar(50) not null
);

create table user_and_authentication (
    uid bigint not null ,
    aid bigint not null
);

user表是存储用户的数据

authentication表是存储权限信息

user_and_authentication是存储用户和权限的关系信息

2.导入需要的依赖坐标

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
    </dependencies>

3.创建对应的实体类

package com.xinghe.domain.po;

import lombok.Data;

/**
 * @author heimi
 * @version 1.0
 * @description 用户
 * @date 2023/5/23 上午11:21
 */
@Data
public class User {
    private Long id;
    private String username;
    private String password;
}
package com.xinghe.domain.po;

import lombok.Data;

/**
 * @author heimi
 * @version 1.0
 * @description 权限
 * @date 2023/5/23 上午11:24
 */
@Data
public class Authentication {
    private Long id;
    private String code;
}

4.创建自定义的用户详情对象,实现UserDetails

package com.xinghe.config;

import com.xinghe.domain.po.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
 * @author heimi
 * @version 1.0
 * @description 自定义用户详情
 * @date 2023/5/23 上午11:27
 */
public class MyUserDetails implements UserDetails {
    
    private User user;
    
    public MyUserDetails(User user) {
        this.user = user;
    }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        String password = user.getPassword();
        user.setPassword(null);
        return password;
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

getAuthorities是和配置权限相关的,这里就先不配置。

isAccountNonExpired账户是否未过期

isAccountNonLocked账户是否未锁定

isCredentialsNonExpired凭据是否未过期

isEnabled账户是否可用

这四个布尔值就先不配置,全设置成true即可,如果有需要可以在数据库添加相应的字段动态的判断这四个值。

需要注意的是这里容器中的用户详情对象需要将密码擦除,防止暴露到端中。

5.编写mapper接口

package com.xinghe.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xinghe.domain.po.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author heimi
 * @version 1.0
 * @description userMapper
 * @date 2023/5/23 上午11:36
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

6.编写认证相关的业务逻辑类

package com.xinghe.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xinghe.config.MyUserDetails;
import com.xinghe.domain.po.User;
import com.xinghe.mapper.UserMapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author heimi
 * @version 1.0
 * @description 业务逻辑
 * @date 2023/5/23 上午11:03
 */
@Service
public class UserService implements UserDetailsService {
    
    @Resource
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        User user = userMapper.selectOne(wrapper);  // 查询用户对象
        MyUserDetails myUserDetails = new MyUserDetails(user);  // 封装用户
        return myUserDetails;
    }
}

这样在用户输入用户名和密码的时候,用户名传到该方法上,然后查询出用户信息,之后根据用户名查询用户数据,然后封装成自定义的UserDetails对象,之后交给SpringSecurity框架,由SpringSecrity进行判断账号是否可用。

授权

在上面的认证操作中,自定义的UserDetails对象中getAuthorities没有配置,接下来进行权限的配置。

1.新建认证相关的实体类

package com.xinghe.config;

import com.xinghe.domain.po.Authentication;
import org.springframework.security.core.GrantedAuthority;

/**
 * @author heimi
 * @version 1.0
 * @description 权限相关
 * @date 2023/5/23 上午11:53
 */
public class MyGrantedAuthority implements GrantedAuthority {
    
    private Authentication authentication;
    
    public MyGrantedAuthority(Authentication authentication) {
        this.authentication = authentication;
    }
    
    @Override
    public String getAuthority() {
        return authentication.getCode();
    }
}

2.修改自定义的UserDetails添加上权限功能

package com.xinghe.config;

import com.xinghe.domain.po.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
 * @author heimi
 * @version 1.0
 * @description 自定义用户详情
 * @date 2023/5/23 上午11:27
 */
public class MyUserDetails implements UserDetails {

    private User user;
    
    private List<MyGrantedAuthority> grantedAuthorities;

    public MyUserDetails(User user, List<MyGrantedAuthority> grantedAuthorities) {
        this.user = user;
        this.grantedAuthorities = grantedAuthorities;
    }
    
    

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return grantedAuthorities;
    }

    @Override
    public String getPassword() {
        String password = user.getPassword();
        user.setPassword(null);
        return password;
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

}

3.编写查询权限的mapper接口

package com.xinghe.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xinghe.domain.po.Authentication;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @author heimi
 * @version 1.0
 * @description authenticationMapper
 * @date 2023/5/23 上午11:37
 */
@Mapper
public interface AuthenticationMapper extends BaseMapper<Authentication> {

    @Select("select * from user_and_authentication uaa inner join authentication a on uaa.aid=a.id where uaa.uid=#{id}")
    List<Authentication> getAuthenticationByUserId(Long id);
}

4.修改认证相关的业务逻辑层

package com.xinghe.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xinghe.config.MyGrantedAuthority;
import com.xinghe.config.MyUserDetails;
import com.xinghe.domain.po.Authentication;
import com.xinghe.domain.po.User;
import com.xinghe.mapper.AuthenticationMapper;
import com.xinghe.mapper.UserMapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author heimi
 * @version 1.0
 * @description 业务逻辑
 * @date 2023/5/23 上午11:03
 */
@Service
public class UserService implements UserDetailsService {

    @Resource
    private UserMapper userMapper;
    
    @Resource
    private AuthenticationMapper authenticationMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        User user = userMapper.selectOne(wrapper);  // 查询用户对象
        // 查询权限
        List<Authentication> authentications = authenticationMapper.getAuthenticationByUserId(user.getId());
        // 封装权限
        List<MyGrantedAuthority> authorityList = authentications.stream().map(MyGrantedAuthority::new).collect(Collectors.toList());
        return new MyUserDetails(user, authorityList);
    }
}

5.针对方法或url授权

针对方法和url授权的话就和前面的基于内存中的对象进行url或者方法授权的步骤是一样的了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值