技术标签: spring spring security java 安全
在日常开发中,几乎所有的项目都需要进行请求的安全校验操作。
通常会采取以下几种方式来实现安全校验
和过滤
。
1、实例化HandlerInterceptor
接口,配置其中的preHandle
、postHandle
、afterCompletion
属性信息。
具体可以参考博客:
2、采取AOP的思想,手写一个接口的拦截过滤。
参考博客:
3、使用一些较为成熟的权限认证、校验框架。如:shiro、security等。
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。
Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正威力在于它可以多么容易地扩展以满足定制需求。
spring-security-core.jar
。包含核心验证和访问控制类接口,远程支持的基本配置API。任何使用Spring Security的应用程序都需要这个模块。支持独立应用程序、远程客户端、服务层方法安全和JDBC用户配置。spring-security-remoting.jar
。提供与 Spring Remoting 集成。spring-security-web.jar
。包括网站安全的模块,提供网站认证服务和基于 URL 访问控制。spring-security-config.jar
。包含安全命令空间的解析代码。如果你使用Spring Security XML命令空间进行配置你需要包含这个模块。 主包名为org.springframework.security.configspring-security-ldap.jar
。LDAP验证和配置代码,如果你需要使用LDAP验证和管理LDAP用户实体,你需要这个模块。spring-security-acl.jar
。ACL专门的领域对象的实现。用来在你的应用程序中应用安全特定的领域对象实例。spring-security-cas.jar
。Spring Security的CAS客户端集成。如果你想用CAS的SSO服务器使用Spring Security网页验证需要该模块。spring-security-openid.jar
。OpenID 网页验证支持。使用外部的OpenID服务器验证用户。 org.springframework.security.openid. 需要 OpenID4Javaspring-security-test.jar
。支持Spring Security的测试。本篇博客基于 SpringBoot
技术架构实现配置和使用操作,配置方式采取IEAD
中的Maven 父子项目
格式进行搭建。
创建Maven父工程,配置整体测试环境的依赖版本管控。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.7.25</log4j.version>
<lombok.version>1.16.18</lombok.version>
<spring.boot.version>2.2.2.RELEASE</spring.boot.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--日志打印-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${log4j.version}</version>
</dependency>
关于如何创建父子项目
,本次不做过多说明。
可以看考博客:idea中Springboot项目如何做成父子结构
子项目中,引入具体的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
启动子项目,访问http://localhost:8080/
观察现象:
此处会自动进入一个登录界面!!!
创建一个新的controller接口,重启项目,然后请求接口测试:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/test1")
public String test1(){
return "test1";
}
}
http://localhost:8080/test1
访问后,依旧是当前的登录界面,此时登录账户密码信息为:
这种情况是由于security
框架,默认生成当前登录密码,存于内存中,进行实现。
但并不适合实际开发使用!
security框架在开发者本身未对其进行设定时,默认登录账户为user
,密码为随机uuid
,生成并存于内存中。
如果想要设定指定的账号密码进行访问控制,可以采取如下几种方式实现:
创建一个application.yaml
的文件,在其中增加如下配置信息:
spring:
security:
user:
name: xiangjiao
password: bunana
重启项目,重新访问http://localhost:8080/test1
,跳转指定的登录界面,输入配置的用户名和密码信息:
通过源码org.springframework.boot.autoconfigure.security.SecurityProperties
类,可以知道其默认的账户、密码生成方式:
但是针对实际的开发逻辑来看,定义死板的方式,并不适合实际的开发需要。
正常来说,验证登录信息,需要从数据库中获取指定账户的账号、密码等。
在security
框架中,与其作用相关的配置信息,都是采取org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
配置类实现配置信息的加载,源码中,针对这个类的描述信息如下所示:
从上图中可以发现:
SecurityAutoConfiguration
自动配置类,依赖有几个其他的配置文件。
其中,针对于验证
的配置类为:org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration
。
其次,spring security
框架本身给开发使用者提供了很多的其他工具类,来实现用户认证操作,如:基于内存
、基于jdbc
等。
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration
编写一个UserServiceImpl.java
类,用于实现UserDetailsService
,并使用重写其中的loadUserByUsername
实现具体的认证操作
。
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.stereotype.Component;
@Component // 由spring管理
public class UserServiceImpl implements UserDetailsService {
// 加载用户信息
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 需要返回 UserDetails 对象实例,
// 查看 UserDetails接口 下的几个子实现类,可以发现有几种类型
// 使用 org.springframework.security.core.userdetails.User 这个子类
UserDetails userDetails = new User("xiangjiao","bunana",
AuthorityUtils.commaSeparatedStringToAuthorityList("add,rm"));
return userDetails;
}
}
重启项目,重新请求http://localhost:8080/test1
,输入用户名和密码信息。
发现:并没有进行跳转!
查看控制台,日志打印信息如下所示:
【问题分析:】
出现
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
问题的原因在于:
security 4
之后,官方对密码编码器
进行了修改操作。
查看org.springframework.security.crypto.factory.PasswordEncoderFactories
源码可以知道security
对其提供很多的编码方式。
如果需要使用其中的指定编码,实现登录验证测试,则需要将设定
的密码
进行编码转换。
转换后如下所示:{noop}xxx
表示无加密
。
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.stereotype.Component;
@Component // 由spring管理
public class UserServiceImpl implements UserDetailsService {
// 加载用户信息
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 需要返回 UserDetails 对象实例,
// 查看 UserDetails接口 下的几个子实现类,可以发现有几种类型
// 使用 org.springframework.security.core.userdetails.User 这个子类
UserDetails userDetails = new User("xiangjiao","{noop}bunana",
AuthorityUtils.commaSeparatedStringToAuthorityList("add,rm"));
return userDetails;
}
}
重启项目,重新请求http://localhost:8080/test1
,发现:
输入用户名、密码后,能够成功跳转。
如果设定加密,则可以按照如下方式配置。
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.bcrypt.BCrypt;
import org.springframework.stereotype.Component;
@Component // 由spring管理
public class UserServiceImpl implements UserDetailsService {
// 加载用户信息
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 需要返回 UserDetails 对象实例,
// 查看 UserDetails接口 下的几个子实现类,可以发现有几种类型
// 使用 org.springframework.security.core.userdetails.User 这个子类
// 无加密
// UserDetails userDetails = new User("xiangjiao","{noop}bunana",
// AuthorityUtils.commaSeparatedStringToAuthorityList("add,rm"));
// 有加密
String encryptPwd = BCrypt.hashpw("bunana",BCrypt.gensalt());
UserDetails userDetails = new User("xiangjiao","{bcrypt}"+encryptPwd,
AuthorityUtils.commaSeparatedStringToAuthorityList("add,rm"));
return userDetails;
}
}
重启项目,重新请求http://localhost:8080/test1
。输入对应的账号、密码信息后,能够成功验证通过。
但是,这种方式一般使用率较低!
每次都需要针对具体的加密解密方式,拼接
{bcrypt}
等太麻烦。
可以考虑使用org.springframework.security.crypto.factory.PasswordEncoderFactories
中的一种加解密
,进行注入使用。下面进行相关测试。
上面的操作中,采取BCrypt.hashpw("bunana",BCrypt.gensalt())
将密码进行加密操作,随后拼接 {bcrypt}
的形式,封装至org.springframework.security.core.userdetails.User
对象实例中,过于繁琐,可以采取如下方式进行设定:
创建一个配置类:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
开启 Security ,
// 但是在 org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration 中的
// org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration 类上
// 已经标注 @EnableWebSecurity
@EnableWebSecurity // 此处不需要该注解进行开启
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 申明 {bcrypt} 对应的 BCryptPasswordEncoder 密码编码器 bean 对象;
* 在进行 cn.security.service.UserServiceImpl#loadUserByUsername(java.lang.String) 时,注入使用
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
创建一个服务类,实现UserDetailsService
,重写loadUserByUsername
。
并将
已创建
的bean
,进行注入
其中。
import org.springframework.beans.factory.annotation.Autowired;
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.Component;
/**
* 自定义身份验证服务类
*/
@Component
public class UserAuthenticationServiceImpl implements UserDetailsService {
// 注入申明的 PasswordEncoder 密码解析器
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 需要返回 UserDetail 实例
UserDetails userDetails = new User("xiangjiao",passwordEncoder.encode("bunana"),
AuthorityUtils.commaSeparatedStringToAuthorityList("add,rm"));
return userDetails;
}
}
将项目重启,进行测试:
http://localhost:8080/test1
【联想:】这里都可以采取注入的方式实现了,也就是说可以注入一个数据库的查询mapper?
除了上面的UserAuthenticationServiceImpl
,将其交给Spring
管理之外,也能够采取手动配置的方式将UserAuthenticationServiceImpl
加载。用来时间验证操作
,如下所示:
创建service文件:
import org.springframework.beans.factory.annotation.Autowired;
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;
// 这里没有交给spring管理
public class UserAuthenticationServiceImpl implements UserDetailsService {
// 注入申明的 PasswordEncoder 密码解析器
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 需要返回 UserDetail 实例
// passwordEncoder.encode("bunana") 会自动进行密码的加密和解析操作
UserDetails userDetails = new User("xiangjiao",passwordEncoder.encode("bunana"),
AuthorityUtils.commaSeparatedStringToAuthorityList("add,rm"));
return userDetails;
}
}
创建对应的配置类:
import cn.security.service.UserAuthenticationServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
开启 Security ,
// 但是在 org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration 中的
// org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration 类上
// 已经标注 @EnableWebSecurity
//@EnableWebSecurity
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 申明 {bcrypt} 对应的 BCryptPasswordEncoder 密码编码器 bean 对象;
* 在进行 cn.security.service.UserServiceImpl#loadUserByUsername(java.lang.String) 时,注入使用
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 方式三:重写 WebSecurityConfigurerAdapter 中的 userDetailsServiceBean。<br/>
* 官方推荐使用 userDetailsServiceBean
* @return
* @throws Exception
*/
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
// 采取手动实例化 指定的 UserAuthenticationServiceImpl 方式
return new UserAuthenticationServiceImpl();
}
}
具体参考gitee中的代码项目 -- springboot-security-02
。
启动项目,重新访问:
http://localhost:8080/test1
参考:springboot-security-03
。
在配置类中,重写 ``WebSecurityConfigurerAdapter
中的configure
。
package cn.security.config;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
开启 Security ,
// 但是在 org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration 中的
// org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration 类上
// 已经标注 @EnableWebSecurity
//@EnableWebSecurity
@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 基于内存配置
auth.inMemoryAuthentication()
.withUser("xiangjiao")
.password(passwordEncoder().encode("bunana"))
.authorities("admin")
.and()
.withUser("123")
.password(passwordEncoder().encode("123456"))
.authorities("role");
}
/**
* 申明 {bcrypt} 对应的 BCryptPasswordEncoder 密码编码器 bean 对象;
* 在进行 cn.security.service.UserServiceImpl#loadUserByUsername(java.lang.String) 时,注入使用
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
请求连接,进行测试:
http://localhost:8080/test
除了基于 内存
方式之外,还有其他的配置方式,如下所示:
基于 userDetailService
实现。
修改配置类,增加userDetailService
的实现,如下所示:
package cn.security.config;
import cn.security.service.UserAuthenticationServiceImpl;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
开启 Security ,
// 但是在 org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration 中的
// org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration 类上
// 已经标注 @EnableWebSecurity
//@EnableWebSecurity
@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {
/**
* 由于交给spring进行管理bean的生命周期,此处可以直接注入
*/
@Autowired
private UserAuthenticationServiceImpl userAuthenticationService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 基于内存配置
// auth.inMemoryAuthentication()
// .withUser("xiangjiao")
// .password(passwordEncoder().encode("bunana"))
// .authorities("admin")
// .and()
// .withUser("123")
// .password(passwordEncoder().encode("123456"))
// .authorities("role");
// 基于 userDetailService
// UserAuthenticationServiceImpl 实现 UserDetailsService,可以直接传递
auth.userDetailsService(userAuthenticationService);
}
/**
* 申明 {bcrypt} 对应的 BCryptPasswordEncoder 密码编码器 bean 对象;
* 在进行 cn.security.service.UserServiceImpl#loadUserByUsername(java.lang.String) 时,注入使用
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
增加UserAuthenticationServiceImpl
这个UserDetailsService
的子实现类:
package cn.security.service;
import org.springframework.beans.factory.annotation.Autowired;
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.Component;
@Component // 交给spring管理
public class UserAuthenticationServiceImpl implements UserDetailsService {
// 注入申明的 PasswordEncoder 密码解析器
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 需要返回 UserDetail 实例
// passwordEncoder.encode("bunana") 会自动进行密码的加密和解析操作
UserDetails userDetails = new User("xiangjiao",passwordEncoder.encode("bunana"),
AuthorityUtils.commaSeparatedStringToAuthorityList("add,rm"));
return userDetails;
}
}
当然,
UserAuthenticationServiceImpl
也可以不交给Spring管理,此时则需要采取new的方式创建。
项目整体布局如下所示:
项目运行后,请求测试:
http://localhost:8080/test
本篇博客介绍了 C语言 的编译和链接,并在后续主要介绍了编译部分。
Ubuntu18.04系统下,makefile/gcc对C语言程序的编译及应用
TMS320C54x系列DSP中的应用 摘要:详细分析了TMS320C54x系列DSP的中断机制,以及在扩展地址模式下中断控制所具有的一些特点,并给出了DSP/BIOS下中断的管理。 关键词:中断 中断向量表 TMS320C54x DSP/BIOS DSP中断是嵌入式芯片的灵魂,这是因为多数嵌入式系统对实时性都有很高的要求,即对出现事件的
Spring-session+mongodb实现session共享pom.xml配置web.xml配置applicationContext.xml 核心配置文件在系统中使用sessionpom.xml配置首先需要引入依赖包,直接在pom.xml中添加以下代码即可。 &amp;lt;!-- spring-session整合mongodb --&amp;gt; &amp;lt;dependency&amp;gt; ...
哪个大佬看看我这个vs,是什么情况啊,难死我了,c-free能运行出来,怎么vs就不行了啊。
SVN1.4.5Server架设方法(转载)
引导Windows环境下JvisulaVM一般存在于安装了JDK的目录${JAVA_HOME}/bin/JvisualVM.exe,它支持(本地和远程)jstatd和JMX两种方式连接远程JVM。jstatd (Java Virtual Machine jstat Daemon)——监听远程服务器的CPU,内存,线程等信息JMX(Java Management Extensions...
项目属性-&gt;连接器-&gt;输入-&gt;附加依赖项里加入ws2_32.lib
文章目录1.前言2.Webpack是什么?有什么用?3.为什么选择Webpack?webpack的优势在哪?3. 如何安装 webpack?4. entry,chunk,bundle是什么?4.1 entry4.2 chunk4.3 bundle5.资源入口6. webpack入门应用6. path的join和resolve区别6.1 path.join6.2 path.resolve1.前言上一篇文章说了一篇文章带你玩转前端所有模块化,有了模块化,那么肯定需要模块化打包工具,那么从这一篇文章开始,我将
Tomcat启动时启动监听器在Tomcat启动时,随即执行相关的逻辑。编写监听器代码创建TomcatListener:public class TomcatListener implements ServletContextListener并重写以下两个方法:public void contextInitialized(ServletContextEvent servletConte...
代码如下:1. FileAndDirectoryInfo类(获取文件及目录信息)// Fig. 15.2: FileAndDirectoryInfo.java// File class used to obtain file and directory information.package ch15;import java.io.IOException;import java.n
fuseki 在线更新数据库命令:s-update --service=http://localhost:3030/ds/update --file=update.rufuseki 的官网上写的命令是 s-update --service http://localhost:3030/ds/update --file=update.ru,其中--service之后没有写“=”,有误。