什么是 SpringSecurity?
Spring Security 是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
百度百科地址:https://baike.baidu.com/item/spring%20security/8831652?fr=aladdin
SpringCloud Security 是基于 Spring Security 为基础而开发的,因此我们学习了 SpringSecurity 就可以移植到 SpringCloud Security 了,详见中文社区网:https://www.springcloud.cc/
Security 应用场景
Security 在很多企业中作为后台角色权限框架、授权认证 Oauth2.0 、安全防护(防止跨站点请求)、Session攻击、非常容易融合SpringMVC使用等。
先下载本篇博客代码:https://pan.baidu.com/s/1R0Daoh7Zdw1-DQKrLYAbkw 提取码:pshm
本篇代码例子,主要是一个入门的 demo:有2个角色 admin 和 user,其中 admin 有增删改查权限,user 只有读的权限。通过整合 SpringSecurity 和 SpringBoot 来做权限控制,很多代码目前先写固定,主要是为了方便学习,后续会从数据库中读取。
SpringBoot 版本号:2.2.2.RELEASE,SpringCloud 版本号:Hoxton.SR2
首先,代码结构:
在 pom.xml 里,我们需要增加 security 的依赖和 freemarket 的依赖,主要做一些页面模板:
<!-- springboot整合freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-->spring-boot 整合security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
然后,templates 目录下的文件都比较简单,使用的是 freemarker 模板,需要提一点的就是 login 文件,用户名和密码是与 security 要求的字段名一致,然后在地址里判断是否有 error 而判断是登录失败,实际项目开发,不应该这样判断,目前是为了学习需要。
然后,WebServerAutoConfiguration 配置类,主要是为了自定义 WEB 服务器参数 可以配置默认错误页面。
package com.study.config;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
/**
* @author biandan
* @description 自定义 WEB 服务器参数 可以配置默认错误页面
* @signature 让天下没有难写的代码
* @create 2021-05-30 下午 9:28
*/
@Configuration
public class WebServerAutoConfiguration {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
ErrorPage errorPage400 = new ErrorPage(HttpStatus.BAD_REQUEST, "/error/400");
ErrorPage errorPage401 = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/401");
ErrorPage errorPage403 = new ErrorPage(HttpStatus.FORBIDDEN, "/error/403");
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
ErrorPage errorPage415 = new ErrorPage(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "/error/415");
ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");
factory.addErrorPages(errorPage400, errorPage401, errorPage403, errorPage404, errorPage415, errorPage500);
return factory;
}
}
最后,需要讲解配置类:SecurityConfig
package com.study.config;
import com.study.handler.MyAuthenticationFailureHandler;
import com.study.handler.MyAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author biandan
* @description
* @signature 让天下没有难写的代码
* @create 2021-05-30 下午 9:38
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAuthenticationSuccessHandler successHandler;
@Autowired
private MyAuthenticationFailureHandler failureHandler;
// 配置认证用户信息和权限
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 添加admin账号,拥有增删改查权限
auth.inMemoryAuthentication().withUser("admin").password("123").
authorities("showUser","addUser","updateUser","deleteUser");
// 添加user账号,只有查询权限
auth.inMemoryAuthentication().withUser("user").password("456")
.authorities("showUser");
}
// 配置拦截请求资源
@Override
protected void configure(HttpSecurity http) throws Exception {
// 如何权限控制 给每一个请求路径 分配一个权限名称 然后账号只要关联该名称,就可以有访问权限
http.authorizeRequests()
// 配置查询用户权限
.antMatchers("/showUser").hasAnyAuthority("showUser")
.antMatchers("/addUser").hasAnyAuthority("addUser")
.antMatchers("/updateUser").hasAnyAuthority("updateUser")
.antMatchers("/deleteUser").hasAnyAuthority("deleteUser")
.antMatchers("/login").permitAll()
.antMatchers("/**").fullyAuthenticated().and()
.formLogin()
.loginPage("/login")
.successHandler(successHandler).failureHandler(failureHandler)
.and().csrf().disable();
}
/* 不推荐使用这种方式,查看博客:https://blog.csdn.net/alinyua/article/details/80219500
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
*/
@Bean
public static PasswordEncoder passwordEncoder(){
DelegatingPasswordEncoder encoder = (DelegatingPasswordEncoder)PasswordEncoderFactories.createDelegatingPasswordEncoder();
encoder.setDefaultPasswordEncoderForMatches(NoOpPasswordEncoder.getInstance());
return encoder;
}
}
需要注意的是我们需要增加 PasswordEncoder 的 Bean 注解,如果不增加会报错:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
原因是升级为 Security5.0 以上密码支持多种加密方式,我们需要恢复以前的模式,不推荐使用 NoOpPasswordEncoder 而是使用 PasswordEncoder。
我们还要禁用 csrf ,同时因为我们使用的是 FromLogin 的方式,如果不禁掉 csrf,必须在表单里传递 token 才能通过。我们设置登录不拦截,代码如下:
.antMatchers("/login").permitAll()
然后,我们需要增加认证成功处理类:
package com.study.handler;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author biandan
* @description 认证成功
* @signature 让天下没有难写的代码
* @create 2021-05-30 下午 9:43
*/
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
String username = httpServletRequest.getParameter("username");
System.out.println("认证成功,username="+username);
//认证成功后跳转到主页
httpServletResponse.sendRedirect("/");
}
}
认证失败处理类:
package com.study.handler;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author biandan
* @description 认证失败
* @signature 让天下没有难写的代码
* @create 2021-05-30 下午 9:45
*/
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
String username = httpServletRequest.getParameter("username");
System.out.println("认证失败,username="+username);
//重定向到登录页面,同时在地址后面增加 error 参数
httpServletResponse.sendRedirect("/login?error");
}
}
application.yml 配置:
server:
port: 80
# 将SpringBoot项目作为单实例部署调试时,不需要注册到注册中心
eureka:
client:
fetch-registry: false
register-with-eureka: false
spring:
application:
name: security-server
# 配置freemarker
freemarker:
# 设置模板后缀名
suffix: .ftl
# 设置文档类型
content-type: text/html
# 设置页面编码格式
charset: UTF-8
# 设置页面缓存
cache: false
# 设置ftl文件路径
template-loader-path:
- classpath:/templates
# 设置静态文件路径,js,css等
mvc:
static-path-pattern: /static/**
OK,启动我们的微服务,浏览器地址输入:http://127.0.0.1/
输入 admin 账号,123密码,登录成功:
admin 有增删改查的权限,因此点击每个按钮都有权限。
然后我们切换到 user 账号,user 账号只有查询的权限,点击增删改提示权限不足。
同时我们查看后台日志:
OK,security 的核心流程我们讲解完毕,后面会讲解如何动态的分配权限。这篇博客仅仅做入门,实际开发不可能写固定用户名和密码、权限到代码里去的。
本篇博客代码:https://pan.baidu.com/s/1R0Daoh7Zdw1-DQKrLYAbkw 提取码:pshm