SpringBoot 整合 SpringSecurity

WebSecurityConfiguration
其中注入了springSecurityFilterChain这个Bean,这是Spring Secuity的核心过滤器, 这是请求的认证入口。
@EnableGlobalAuthentication
其中注入了AuthenticationConfiguration配置类,这个类的主要作用是,向spring容器中注入AuthenticationManagerBuilder, AuthenticationManagerBuilder其实是使用了建造者模式, 他能建造AuthenticationManager, 是身份认证的入口。
EnableWebSecurity注解有两个作用,1: 加载了WebSecurityConfiguration配置类, 配置安全认证策略。2: 加载了AuthenticationConfiguration, 配置了认证信息。

Spring Secutity

Spring Security是一个为系统提供声明式的安全访问控制的轻量级框架。它为应用程序提供身份验证和授权支持。

SpringSecurity 是基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,在 Web 请求级和方法调用级处理用户认证(Authentication)和用户授权(Authorization)。他提供了强大的企业安全服务,如:认证授权机制、Web资源访问控制、业务方法调用访问控制、领域对象访问控制Access Control List(ACL)、单点登录(SSO)等等。

Spring Security在架构上将认证与授权分离,并提供了扩展点。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好的集成 ,并配备了流行的安全算法实现捆绑在一起。

核心功能:认证(你是谁)、授权(你能干什么)、攻击防护(防止伪造身份)。

基本原理

SpringSecurity的核心是一个过滤器链,即一组Filter,所有的请求都会经过这些过滤器,通过拦截url从而实现权限的控制。每个过滤器都有特定的职责,可通过配置添加、删除过滤器。过滤器的排序很重要,因为它们之间有依赖关系。有些过滤器也不能删除,如处在过滤器链最后几环的ExceptionTranslationFilter(处理后者抛出的异常),FilterSecurityInterceptor(最后一环,根据配置决定请求能不能访问服务)。

用户认证与授权

Spring Security 基于 Spring 框架,一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。

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

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

解决安全性问题

Spring Security主要是从两个方面解决安全性问题:
web请求级别:使用servlet过滤器保护web请求并限制URL级别的访问
方法调用级别:使用Spring AOP保护方法调用,确保具有适当权限的用户采用访问安全保护的方法.

SpringSecurity细分:

Web/Http 安全:这是最复杂的部分。通过建立 filter 和相关的 service bean 来实现框架的认证机制。当访问受保护的 URL 时会将用户引入登录界面或者是错误提示界面。
业务对象或者方法的安全:控制方法访问权限的。
AuthenticationManager:处理来自于框架其他部分的认证请求。
AccessDecisionManager:为 Web 或方法的安全提供访问决策。会注册一个默认的,但是我们也可以通过普通 bean 注册的方式使用自定义的 AccessDecisionManager。
AuthenticationProvider:AuthenticationManager 是通过它来认证用户的。
UserDetailsService:跟 AuthenticationProvider 关系密切,用来获取用户信息的。

SpringBoot 整合Spring Security(二)JWT Token认证

SpringBoot 整合Spring Security(一) 自定义用户名密码认证

表单输入用户名密码,实现用户名数据库查询,密码校验
SpringBoot2.1.5

WebSecurityConfig

http.formLogin(); //输入表单用户名密码

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    @Autowired
    private UserDetailsService userDetailsService;

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

    @Override // 配置用户认证, 使用自定义身份验证组件
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); }
    
    public void configure(HttpSecurity http)throws Exception{
        http.formLogin();

        http.rememberMe().and()
                .cors().and().csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                //.antMatchers("/").permitAll()
                .antMatchers("/admin/**").permitAll()
                //.antMatchers("/user/**").permitAll()
                .anyRequest().authenticated();// 其他所有请求需要身份认证
    }}

UserDetailsService 具体实现

import org.springframework.security.core.authority.AuthorityUtils;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Resource
    private PasswordEncoder passwordEncoder;
    
    private final List<User> userList = new ArrayList<>();

    @PostConstruct
    public void initData(){
        String password = passwordEncoder.encode("123456");
        userList.add(new User("admin", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")));
        userList.add(new User("user1", password, AuthorityUtils.commaSeparatedStringToAuthorityList("user")));
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("loadUserByUsername----" + username);
        return userList.get(0);
    }
}

UserController

@RestController
public class UserController {

    @GetMapping("/user/getCurrentUser")
    public Object getCurrentUser(Authentication authentication){
        log.info("/user/getCurrentUser ok");
        return "user01 -----" +  authentication;
    }
}

测试接口

输入admin 123456访问接口
http://localhost:9507/user/getCurrentUser

请求结果

UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=admin, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[admin]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=974646B19094BBB5A624D56061E7D507], Granted Authorities=[admin]]

(一)完

SpringBoot 整合Spring Security(一) 基础配置及说明

引入依赖

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

测试类

@RestController
public class UserController
{
	@RequestMapping(value = "/test")
	public String test() throws Exception
	{
		return "test success";
	}
}

在不做任何配置的情况下,security会把服务内所有资源的访问都保护起来,需要先进行身份证认证才可访问, 使用默认的表单登录或http basic认证方式。

访问测试类

启动SpringBoot,访问http://localhost:8080/test,由于我们开启了SpringSecurity且当前是未登录状态,页面会被302重定向到http://localhost:8080/login登录页面。

用户名:user,密码在控制台输出中:d2ee3df0-1924-440a-9b8e-7f3449ee798f

输入正确的用户名密码点击登录,系统重新302到http://localhost:8080/test并显示返回的数据。即:这表示我们的接口已经被spring保护了。

设定用户名和密码

在application.properties中做如下设定,使用用户名admin密码123访问系统。
spring.security.user.name=admin
spring.security.user.password=123

Spring Security 设置用户名和密码——自定义编写实现类
##### 第三种方式:重写UserDetailsService

Spring Security 配置

新建Java类WebSecurityConfigurer
使用@Configuration和@EnableWebSecurity对Java类进行注解声明配置,继承WebSecurityConfigurerAdapter。

@EnableWebSecurity

使用@Configuration和@EnableWebSecurity对Java类进行配置,继承WebSecurityConfigurerAdapter,对里面的方法重写就可以了(这样Springboot将不会初始化默认的WebSecurityConfigurerAdapter ),分别是对AuthenticationManagerBuilder,WebSecurity,HttpSecurity方法,我们主要介绍AuthenticationManagerBuilder和HttpSecurity,通过对这两种方法重写最终实现我们自定义认证;

@EnableGlobalMethodSecurity注解详解

当开启spring方法级安全时,只需要在任何 @Configuration实例上使用 @EnableGlobalMethodSecurity 注解就能达到此目的。这个注解为我们提供了prePostEnabled 、securedEnabled 和 jsr250Enabled 三种不同的机制来实现同一种功能

prePostEnabled = true 会解锁 @PreAuthorize 和 @PostAuthorize 两个注解。从名字就可以看出@PreAuthorize 注解会在方法执行前进行验证,而 @PostAuthorize 注解会在方法执行后进行验证。
原文链接:https://blog.csdn.net/chihaihai/article/details/104678864

重写UserDetailsService

设置用户名和密码——自定义编写实现类UserDetailsService

想要关闭UserDetailsService的自动配置,则添加一个类实现UserDetailsService,改变认证的用户信息来源,我们可以实现 UserDetailsService。AuthenticationProvider或AuthenticationManager 来覆盖Springboot默认实现。

认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider。当我们使用 authentication-provider 元素来定义一个 AuthenticationProvider 时,如果没有指定对应关联的 AuthenticationProvider 对象,Spring Security 默认会使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名、密码和所拥有的权限等。所以如果我们需要改变认证的方式,我们可以实现自己的 AuthenticationProvider;如果需要改变认证的用户信息来源,我们可以实现 UserDetailsService。

设定多个用户

通过管理器增加了两个用户:admin,admin;guest,guest。
InMemoryUserDetailsManager:顾名思义,将用户名密码存储在内存中的用户管理器。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                .passwordEncoder(new MyPasswordEncoder())
                .withUser("admin").password("admin").roles("");
                            
            auth.inMemoryAuthentication()
            .passwordEncoder(new MyPasswordEncoder())
            .withUser("guest").password("guest").roles("");
    }
}

MyPasswordEncoder.java

public class MyPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }
    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(charSequence.toString());
    }
}

.
.
.

权限管理系统

实现用户、角色、权限的动态管理
在这里插入图片描述

数据库

实现用户、角色、权限的动态管理

数据库表为5个分别是: 用户表、角色表、权限表、用户角色中间表、角色权限中间表

用户表

drop table if exists `user`;
create table if not exists `user`(
 id int(10)  not null primary key AUTO_INCREMENT,
 username varchar(100),
 password varchar(100),
 token varchar(100)
) Engine=InnoDB DEFAULT charset=utf8;

insert into user(username,password) values("admin","$2a$10$XTyME7htOf3pYO0MsUcWCOW4nkpnJ1NDk9TLF4FbpmQxc0qrrsqsm");
insert into user values(2,"user","$2a$10$XTyME7htOf3pYO0MsUcWCOW4nkpnJ1NDk9TLF4FbpmQxc0qrrsqsm", null);

角色表Role

drop table if exists `role`;
create table if not exists `role`(
	id int(10)  not null  primary key  AUTO_INCREMENT  COMMENT '角色编号' ,
	name varchar(100)  COMMENT '角色名称' 
) Engine=InnoDB DEFAULT charset=utf8 COMMENT = '角色表';
--insert into role(name) values("ROOT");
insert into role(name) values("ROLE_ADMIN");
insert into role(name) values("ROLE_USER");
drop table if exists `role_user`;
create table if not exists `role_user`(
	rid int(10) not null,
	uid int(10) not null,
	unique `uk_rid_uid`(rid, uid)
) Engine=InnoDB DEFAULT charset=utf8 COMMENT = '用户角色中间表';

insert into role_user values(1, 1);
insert into role_user values(2, 2);

Permission表

drop table if exists `Permission`;
create table if not exists `Permission`(
	id int(10) not null primary key,
	text varchar(100),
	href varchar(100),
	pid int(100),
	descx varchar(100)
);
insert into Permission values(100, "控制台", "/", 100, "index");
insert into Permission values(200, "基础管理", "/manage", 200, "jichu");
insert into Permission values(210, "部门管理", "/dept.html", 200, "DeptManage");
insert into Permission values(220, "用户管理", "/user", 200, "UserManage");
insert into Permission values(230, "角色管理", "/role.html", 200, "DeptManage");
insert into Permission values(240, "角色用户表", "/roleUser", 200, "DeptManage");
insert into Permission values(250, "角色权限表", "/rolePermission", 200, "DeptManage");
insert into Permission values(260, "菜单管理", "/Menu", 200, "菜单管理descx");

User.java实体类

public class User implements UserDetails {
	public int id;
	public String username;
	public String password;
	public List<Role> roles;
	public List<Permission> permissions;

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<GrantedAuthority> authorities = new ArrayList<>();
        for(Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
		return authorities;
	}
	@Override
	public boolean isAccountNonExpired() { return true; }
	@Override
	public boolean isAccountNonLocked() { return true; }
	@Override
	public boolean isCredentialsNonExpired() { return true; }
	@Override
	public boolean isEnabled() { return true; }

Role.java

public class Role{
	public int id;
	public String name;
	//getter、setter
	...

Permission.java
与BootStrap-treeview插件所返回的JSON参数一致,则前端可直接引入data:result。

public class Permission implements Serializable {
	private static final long serialVersionUID = 1L;
	//权限id
	public int id;
	//权限名称
	public String text;
    //授权链接
    public String href;
    //权限类型(menu、button)
    //public String type;
    //权限描述
    public String descx;
    //父节点id
    public int pid;
    //子节点
    public List<Permission> nodes = new ArrayList<>();
    

RoleUser.java

public class RoleUser{
	public int uid;
	public int rid;
	//getter、setter
	...

RolePermission.java

public class RolePermission{
	public int rid;
	public int pid;
	//getter、setter
	...

WebSecurityConfig.java

关闭CSRF跨域 CSRF(Cross-site request forgery)跨站请求伪造,也被称为one-click attack单键攻击;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private LoginAuthenticationSuccess authenticationSuccess;//验证成功的处理类
    
	@Bean
	protected UserDetailsService userDetailsService()	{
		return new UserDetailsServiceImpl();
	}
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    	auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
    	System.out.println("configure(AuthenticationManagerBuilder auth)...");
    }
	
	@Override
	public void configure(HttpSecurity http) throws Exception	{
		http
			.authorizeRequests() //其他地址的访问均需验证权限
			.anyRequest()
			.authenticated()
			.and()
		.formLogin()
			.loginPage("/login.html")
			.loginProcessingUrl("/loginSub")
			//.defaultSuccessUrl("/")
			.successHandler(authenticationSuccess)
			.failureUrl("/failure.html")
			.permitAll()
			.and()
		.logout()
			.logoutUrl("/logout")
			.permitAll();
		http.csrf().disable();
		//
		//http.sessionManagement().invalidSessionUrl("/session/invalid"); //session失效时间
		//http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); //无权访问处理器
	}

    @Override
    public void configure(WebSecurity web) throws Exception {
    	System.out.println("configure(WebSecurity web)...");
    	//解决静态资源被拦截的问题
    	web.ignoring().antMatchers("/static/**");
    	web.ignoring().antMatchers("/bootstrap/**");
        web.ignoring().antMatchers("/css/**");
        web.ignoring().antMatchers("/js/**");
        web.ignoring().antMatchers("/images/**");
        web.ignoring().antMatchers("/**/favicon.ico");
        web.ignoring().antMatchers("/lib/**");
        web.ignoring().antMatchers("/fonts/**");
        web.ignoring().antMatchers("/lang/**");
        web.ignoring().antMatchers("/login.html");
        //解决服务注册url被拦截的问题
        web.ignoring().antMatchers("/**/*.json");
    }
}

LoginAuthenticationSuccess.java

@Component
public class LoginAuthenticationSuccess extends SimpleUrlAuthenticationSuccessHandler {
	@Autowired
	private ObjectMapper objectMapper;
	
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException
	{
		System.out.println("---Authentication Success(登录成功): " + authentication); 
		System.out.println("---getPrincipal: " + authentication.getPrincipal());
		response.setContentType("application/json;charset=UTF-8");
		response.getWriter().write(objectMapper.writeValueAsString(authentication));
	}
}

UserDetailsService

UserDetailsServiceImpl.java
父子菜单权限区分

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
	@Autowired
	private LoginDao loginDao;
	@Autowired
	private PermissionDao permissionDao;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = loginDao.loadUserByUsername(username);
		if(user == null) {
            throw new UsernameNotFoundException("用户名不存在!" + username);
        }
		List<Permission> permissions = permissionDao.findPermissionsByUserId(user.id);
		List<Permission> pers = new ArrayList<>();
		
		for(Permission p : permissions)	{
			if(p.id == p.pid) { //200/200
				pers.add(p);
			}
		}
		pers.forEach(e ->
		{
			for (Permission p : permissions) {
				if (p.id != p.pid && p.pid == e.pid) //210/200
				{
					e.nodes.add(p);
				}
			}
		});
		System.out.println("Permissions列表数据:" + pers);
		user.permissions = pers;
        return user;
	}
}

Redis缓存

192.168.236.128:6379> keys *
1) "spring:session:sessions:a1287a8d-bb00-4a3f-9335-da69b14d4510"
2) "dept_list::count"
3) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:admin"
4) "spring:session:sessions:expires:a1287a8d-bb00-4a3f-9335-da69b14d4510"
5) "spring:session:expirations:1571032080000"
6) "dept_list::0"

其他

限制最大登录数

config()中添加

.sessionManagement()
	.invalidSessionUrl("/login/invalid")
	.maximumSessions(1)
	// 当达到最大值时,是否保留已经登录的用户
	.maxSessionsPreventsLogin(false)
	// 当达到最大值时,旧用户被踢出后的操作
    .expiredSessionStrategy(new CustomExpiredSessionStrategy())

loginProcessingUrl

如果只配置loginPage而不配置loginProcessingUrl的话
那么loginProcessingUrl默认就是loginPage
你配置的loginPage(“/testpage.html”) ,那么loginProcessingUrl就是"/testpage.html"

优先级从上向下:
.successHandler(authenticationSuccess)
.successHandler(new ForwardAuthenticationSuccessHandler(“/index2”))
.defaultSuccessUrl(“/index1”)

参考资料

Spring Boot2.0使用Spring Security
https://www.cnblogs.com/wtzbk/p/9387859.html

关于Boot应用中集成Spring Security你必须了解的那些事
https://www.bbsmax.com/A/A2dmY2DWde/

Spring Boot2.0 集成Security QQ登录实现APP ID、APP Key
https://blog.csdn.net/qq_35508033/article/details/79046441

Spring Security OAuth2 SSO
https://www.cnblogs.com/cjsblog/p/9296361.html

基于SpringBoot搭建应用开发框架(二) —— 登录认证
https://www.cnblogs.com/chiangchou/p/springboot-2.html#_label2_2

SpringBoot2.x整合Security5(完美解决 There is no PasswordEncoder mapped for the id “null”)
https://blog.csdn.net/SWPU_Lipan/article/details/80586054
https://blog.csdn.net/qq_21963133/article/details/81066714

.
SpringBoot+Vue前后端分离,使用SpringSecurity完美处理权限问题
https://blog.csdn.net/u012702547/article/details/79135833

SpringSecurity自定义多Provider时提示No AuthenticationProvider found for问题的解决方案与原理(一)
https://blog.csdn.net/u012760435/article/details/126558412

SpringSecurity原理剖析与权限系统设计
http://www.mamicode.com/info-detail-2792826.html

springSecurity安全框架的学习和原理解读
https://blog.csdn.net/liushangzaibeijing/article/details/81220610
.

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值