安全可以说是公司的红线了, 一般项目都会有严格的认证和授权操作,在 Java 开发领域常见安全框架有 Shiro Spring Security。Shiro 一个轻量级的安全管理框架,提供了认证、授权、会话管理、密码管理、缓存管理等功能, Spring Security 是一个相对复杂的安全管理框架,功能比 Shiro 更加强大,权限控制细粒度更高,对 OAuth 2 的支持也更友好,又因为 Spring Security 源自 Spring 家族,因此可以和 Spring 框架无缝整合,特别是 Spring Boot 中提供的自动化配置方案,可以让 Spring Security 的使用更加便捷。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
@RestController
public class HelloController {
@GetMapping("hello")
public String hello(){
return "hello";
}
}
项目启动成功后,访问/hello 接口会自动跳转到登录页面,这个登录页面是由 Spring Security 提供的。
默认的用户名是 user ,默认的登录密码则在每次启动项目时随机生成 查看项目启动日志,
如果开发者对默认的用户名和密码不满意,可以在 application.properties 中配置默认的用户名、密码以及用户角色,配置方式如下:
spring.security.user.name=wi-gang
spring.security.user.password=123456
spring.security.user.roles=admin
当开发者在 application.properties 中配置了默认的用户名和密码后,再次启动项目,项目启动日志就不会打印出随机生成的密码了,用户可直接使用配置好的用户名和密码登录,登录成功后,用户还具有 个角色一-admin
开发者也可以自定义类继承自 WebSecurityConfigurerAdapter ,进而实现对 Spring Security 更多的自定义配置,例如基于内存的认证,配置方式如下:
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@CacheConfig
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
/**
* 功能描述:,在该方法中配直两个用户,一个用户名是 admin ,密码是 123456,具备两个角色 ADMIN 和 USER,另一个用户名是 wi-gang ,密码是 123456 ,具备一个角色 USER
* @author wi-gang
* @date 2022/1/23 11:28 下午
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("123456").roles("ADMIN","USER")
.and()
.withUser("wi-gang").password("123456").roles("USER");
}
}
虽然现在可以实现认证功能,但是受保护的资源都是默认的 ,而且也不能根据实际情况进行角色管理,如果要实现这些功能 就需要重写 WebSecurityConfigurerAdapter 的另一个方法,代码如下:
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
/**
* 功能描述:在该方法中配直两个用户,一个用户名是 admin ,密码是 123456,具备两个角色 ADMIN 和 USER,另一个用户名是 wi-gang ,密码是 123456 ,具备一个角色 USER
* @author wi-gang
* @date 2022/1/23 11:28 下午
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("root").password("123").roles("ADMIN", "DBA")
.and()
.withUser("admin").password("123456").roles("ADMIN", "USER")
.and()
.withUser("wi-gang").password("123456").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.access("hasAnyRole('ADMIN','USER')")
.antMatchers("/db/**")
.access("hasRole('ADMIN') and hasRole('USER')")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
//关闭 csrf
.csrf()
.disable();
}
}
代码解释:
authorizeRequests()
方法开启 HttpSecurity 的配置,antMatchers()
方法到access()
方法分别表示用户访问admin/**
模式的 URL 必须具备 ADMIN 的角色;用户访问/user/**
模式的 URL必须具备 ADMIN 或 USER 的角色;用户访问/db/**
模式的 URL 必须具备 ADMIN 和 DBA 的角色。anyRequest()
、authenticated()
表示除了前面定义的 URL 模式之外,用户访问其他的 URL 都必须认证后访问(登录后访问)formLogin() .loginProcessingUrl("/login").permitAll()
几行表示开启表单登录,即项目启动后看到的登录页面,同时配置了登录接口为/login
,即可以直接调用/login
接口,发起一个 POST 请求进行登录,登录参数中用户名必须命名为 username ,密码必须命名为 password ,配置 loginProcessingUrl 接口主要是方使 AJax 或者移动端调用登录接口 。最后还配置了 permitAll()
,表示和登录相关的接口都不需要认证即可访问。配置完成后,接下来在 Controller 中添加如下接口进行测试:
@RestController
public class HelloController {
@GetMapping("hello")
public String hello(){
return "hello";
}
@GetMapping("/admin/hello")
public String admin(){
return "hello admin";
}
@GetMapping("/user/hello")
public String user(){
return "hello user";
}
@GetMapping("/db/hello")
public String db(){
return "hello db";
}
}
根据上面的配置, /admin/hello
接口 root 和 admin 用户具有访问权限;/user/hello
接口admin 和 wi-gang 用户具有访问权限:/db/hello
路径则只有 root 用户具有访问权限。
一般密码账户都是保存在数据库中,如果你的数据库被黑客入侵被盗了,如果你不加密,那么你的账户被盗了,就危险了。而加密的密码就不同了,因为黑客得到的只是你加密后的字符串,而对方可能不知道你的加密算法,破解难度增加,这样你的账户安全性就大大增加了。
密码加密一般会用到散列函数,又称散列算法、哈希函数,这是一种从任何数据中创建数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来,然后将数据打乱混合,重新创建一个散列值。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据会使得数据库记录更难找到。我们常用的散列函数有 MD5 消息摘要算法、安全散列算法(Secure Hash Algorithm )。
但是仅仅使用散列函数还不够,为了增加密码的安全性,一般在密码加密过程中还需要加盐,所谓的盐可以是一个随机数,也可以是用户名,加盐之后,即使密码明文相同的用户生成的密码,密文也不相同,这可以极大地提高密码的安全性。但是传统的加盐方式需要在数据库中有专门的字段来记录盐值,这个字段可能是用户名字段(因为用户名唯 ),也可能是一个专门记录盐值的字段,这样的配置比较烦琐 Spring Security 提供了多种密码加密方案,官方推荐使用 BCryptPasswordEncoder, BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供由 strength 和SecureRandom 实例。strength 越大,密钥的迭代次数越多,密钥迭代次数为2^strength。 strength 取值在 4~31 之间,默认为 10。
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("root").password("123").roles("ADMIN", "DBA")
.and()
.withUser("admin").password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq").roles("ADMIN", "USER")
.and()
.withUser("wi-gang").password("$2a$10$eUHbAOMq4bpxTvOVz331IehLe3fu6NwqC9tdOcxJXEhyZ4simqXTC").roles("USER");
}
这里的密码就是使用 BCryptPasswordEncoder 加密后的密码,虽然 admin、wi-gang 加密后的密
码不一样, 但是明文都是 123 。配置完成后,使用 admin/123 或 wi-gang/123 就可以实现登录。本案
例使用了配置在内存中的用户,一般情况下,用户信息是存储在数据库中的,因此需要在用户注册时对密码进行加密处理,代码如下:
@Service
public class RegService {
public int reg(String username , String password) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
String encodePasswod = encoder.encode(password);
return saveToDb(username, encodePasswod);
}
}
用户将密码从前端传来之后 通过调用 BCryptPasswordEncoder 实例中的 encode 方法对密码进行加密处理,加密完成后将密文存入数据库。
开发者也可以通过注解来灵活地配置方法安全,要使用相关注解,首先要通过@EnableGloba!MethodSecurity 注解开启基于注解的安全配置:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class MyWebSecurityConfig {
}
代码解释:
开启注解安全配置后,接下来创建 MethodService 进行测试,代码如下:
@Service
public class MethodService {
@Secured("ROLE_ADMIN")
public String admin() {
return "admin";
}
@PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
public String adminAndDBA() {
return "adminAndDBA";
}
@PreAuthorize("hasAnyRole('ADMIN','DBA','USER')")
public String hasAnyRole() {
return "hasAnyRole";
}
}
代码解释:
@Secured(”ROLE_ ADMIN")
注解表示访问该方法需要 ADMIN 角色,注意这里需要在角色前加一个前缀ROLE_
@PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
注解表示访问该方法既需要 ADMIN又需要 DBA 角色@PreAuthorize("hasAnyRole('ADMIN','DBA','USER')")
表示访问该方法需要 ADMIN、DBA、USER 角色