Spring Security认证、加密、简单授权
目录
一、创建项目
二、内存认证
方式一:配置文件方式
方式二:配置类中重写configure方法
方式三:配置类中重写userDetailsService方法
方式四 :自定义实现类方式
三、数据库方式
方式1:基于默认数据库模型的认证
方式2:自定义数据库认证
四、加密
五、授权
角色继承
一、创建项目
使用Spingboot快速构建一个项目,引入以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
定义一个访问的Controller
@Controller
public class DemController {
@RequestMapping("/index")
public void index(){
System.out.println("hello");
}
}
启动项目,访问/index,浏览器地址跳转为localhost:8080/login,界面如下
在项目启动过程中,我们会看到如下一行日志:
Using generated security password: d58e2224-2034-4196-b13f-f4a18bb65a13
这就是 Spring Security 为默认用户 user 生成的临时密码,是一个 UUID 字符串。
在登录页面,默认的用户名就是 user,默认的登录密码则是项目启动时控制台打印出来的密码,输入用户名密码之后,就登录成功了,登录成功后,我们就可以访问到 /index接口了。
在 Spring Security 中,默认的登录页面和登录接口,都是 /login
,只不过一个是 get 请求(登录页面),另一个是 post 请求(登录接口)。
默认的密码有一个问题就是每次重启项目都会变。对于security登录认证账户可以来自内存和数据库,如下具体介绍。
二、内存认证
方式一:配置文件方式
默认的用户定义在SecurityProperties里边,是一个静态内部类,如果要定义自己的用户名密码,必然是要去覆盖默认配置,在配置文件中配置
spring.security.user.name=admin
spring.security.user.password=admin
spring.security.user.roles=ADMIN
此时重启项目,就可以使用自己定义的用户名/密码登录了。
方式二:配置类中重写configure方法
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("123")
.password("123").roles("admin");
}
}
- 首先我们自定义 SecurityConfig 继承自 WebSecurityConfigurerAdapter,重写里边的 configure 方法。
- 首先我们提供了一个 PasswordEncoder 的实例,因为目前的案例还比较简单,因此我暂时先不给密码进行加密,所以返回 NoOpPasswordEncoder 的实例即可。
- configure 方法中,我们通过 inMemoryAuthentication 来开启在内存中定义用户,withUser 中是用户名,password 中则是用户密码,roles 中是用户角色。
- 如果需要配置多个用户,用 and 相连
方式三:配置类中重写userDetailsService方法
由于 Spring Security 支持多种数据源,例如内存、数据库、LDAP 等,这些不同来源的数据被共同封装成了一个 UserDetailService 接口,任何实现了该接口的对象都可以作为认证数据源。因此我们还可以通过重写 WebSecurityConfigurerAdapter 中的 userDetailsService 方法来提供一个 UserDetailService 实例进而配置多个用户:
@Configuration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
@Bean
public UserDetailsService userDetailsServiceBean() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin").password("admin").roles("admin").build());
manager.createUser(User.withUsername("123").password("123").roles("user").build());
return manager;
}
}
方式四 :自定义实现类方式
重写UserDetailsService接口中的loadUserByUsername方法,并在配置类型申明
import org.springframework.security.core.GrantedAuthority;
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.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 自定义认证服务实现security中的方法
*/
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
//实现security接口类userdetails唯一方法
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
//这个设置一个角色
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("123",getPass(),authorities);
}
//对配置的用户密码123进行加密
public String getPass(){
return new BCryptPasswordEncoder().encode("123");
}
}
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;
import javax.annotation.Resource;
@Configuration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
//注入自己的实现类
@Resource
private MyUserDetailsServiceImpl userDetailsService;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
总结:以上四种基于内存的方式认证推荐第二种
三、数据库方式
方式1:基于默认数据库模型的认证
UserDetailsService 都有哪些实现类:
可以看到,在几个能直接使用的实现类中,除了 InMemoryUserDetailsManager 之外,还有一个 JdbcUserDetailsManager,使用 JdbcUserDetailsManager 可以让我们通过 JDBC 的方式将数据库和 Spring Security 连接起来。
JdbcUserDetailsManager 自己提供了一个数据库模型,这个数据库模型保存在如下位置:
org/springframework/security/core/userdetails/jdbc/users.ddl
这里面sql语句如下
create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);
create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
可以看到,脚本中有一种数据类型 varchar_ignorecase,这个其实是针对 HSQLDB 数据库创建的,而我们使用的 MySQL 并不支持这种数据类型,所以这里需要大家手动调整一下数据类型,将 varchar_ignorecase 改为 varchar 即可。
修改完成后,创建数据库,执行完成后的脚本。
执行完 SQL 脚本后,我们可以看到一共创建了两张表:users 和 authorities。
- users 表中保存用户的基本信息,包括用户名、用户密码以及账户是否可用。
- authorities 中保存了用户的角色。
- authorities 和 users 通过 username 关联起来。
配置完成后,接下来,我们将 InMemoryUserDetailsManager 提供的用户数据用 JdbcUserDetailsManager 代替掉,如下:
@Autowired
DataSource dataSource;
@Override
@Bean
protected UserDetailsService userDetailsService() {
JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
manager.setDataSource(dataSource);
if (!manager.userExists("admin")) {
manager.createUser(User.withUsername("admin").password("123").roles("admin").build());
}
if (!manager.userExists("123")) {
manager.createUser(User.withUsername("123").password("123").roles("user").build());
}
return manager;
}
- 首先构建一个 JdbcUserDetailsManager 实例。
- 给 JdbcUserDetailsManager 实例添加一个 DataSource 对象。
- 调用 userExists 方法判断用户是否存在,如果不存在,就创建一个新的用户出来(因为每次项目启动时这段代码都会执行,所以加一个判断,避免重复创建用户)。
- 用户的创建方法和我们之前 InMemoryUserDetailsManager 中的创建方法基本一致。
因为要连接数据库,我们还需导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置文件配置
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql:///security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
方式2:自定义数据库认证
Spring Security适应系统,而非让系统适应Spring Security,是Spring Security框架开发者和使用者的共识,上面使用了 InMemoryUserDetailsManager 和 JdbcUserDetailsManager 两个UserDetailsService 实现类。生效方式也很简单,只需加入 Spring 的 IoC 容器,就会被 Spring Security自动发现并使用。自定义数据库结构实际上也仅需实现一个自定义的UserDetailsService。
UserDetailsService仅定义了一个loadUserByUsername方法,用于获取一个UserDetails对象。UserDetails对象包含了一系列在验证时会用到的信息,包括用户名、密码、权限以及其他信息
这里结合mybatis认证,需要导入的依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
自定义类和配置
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserDao userDao;
//实现security接口类userdetails唯一方法
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
SysUser user = userDao.getUserByUserName(userName);
//这里查出结果如:userName = "admin" passWord="$2a$10$FsNmqBMoxqRQAzcjvF8YD.Sqh3SaSkO40FfuC.VraGuKTcTeC3wDm";密码是经过BCryptPasswordEncoder加密的
if(user==null){
throw new UsernameNotFoundException("用户不存在");
}
//这个设置一个角色
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(user.getUserName(),user.getPassWord(),authorities);
}
}
@Configuration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
//注入自己的实现类
@Resource
private MyUserDetailsServiceImpl userDetailsService;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
附:
自定义相关配置
上面的登录地址都是security默认提供的,我们可以自定义一些配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("$2a$10$FsNmqBMoxqRQAzcjvF9YD.Sqh3SaSkO40FfuC.VraGuKTcTeC3wDm").roles("admin");
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**","/img/**","/layui/**");
}
//自定义相关配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//自定义自己编写的登录页面
.loginPage("/login.html")//登录页面设置,默认去static下找页面
.loginProcessingUrl("/adminLogin")//登录访问路径 这个controller不需要我们实现 security帮我们处理
.defaultSuccessUrl("/admin/index")//登录成功之后跳转到自己的controller路径
.and().logout().logoutUrl("/logout")//设置页面退出地址 这个controller不需要我们实现 security帮我们处理
.logoutSuccessUrl("/admin/index")
.and().authorizeRequests()
.antMatchers("/","/adminLogin").permitAll()
.antMatchers("/admin/**").authenticated()
.anyRequest().permitAll()
.and().csrf().disable();
}
}
form表单 需要放在boot项目下的static路径里
<form action="/adminLogin" method="post" class="layui-form layui-form-pane" style="width: 24%;margin: auto;margin-top: auto;text-align: center;background-color: #f8f8f8;padding: 20px;padding: 49px 107px;">
<img style="width: 100%;margin-bottom: 20px;" th:src="@{/static/img/logo.png}">
<div class="layui-form-item">
<label class="layui-form-label">账户</label>
<div class="layui-input-block">
<input type="text" name="username" placeholder="账户" class="layui-input" >
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="password" name="password" placeholder="密码" class="layui-input">
</div>
</div>
<button type="submit" class="layui-btn layui-btn-lg">登录</button>
</form>
- web.ignoring() 用来配置忽略掉的 URL 地址,一般对于静态文件,我们可以采用此操作。
- 如果我们使用 XML 来配置 Spring Security ,里边会有一个重要的标签
<http>
,HttpSecurity 提供的配置方法 都对应了该标签。 - authorizeRequests 对应了
<intercept-url>
。 - formLogin 对应了
<formlogin>
。 - and 方法表示结束当前标签,上下文回到HttpSecurity,开启新一轮的配置。
- permitAll 表示登录相关的页面/接口不要被拦截。
- 最后记得关闭 csrf
- 当我们定义了登录页面为 /login.html 的时候,Spring Security 也会帮我们自动注册一个 /login.html 的接口,这个接口是 POST 请求,用来处理登录逻辑。
配置完成后,再去重启项目,此时访问任意页面,就会自动重定向到我们定义的这个页面上来,输入用户名密码就可以重新登录了。
四、加密
Spring Security 中加密是一个很简单却又不能忽略的模块,数据只有加密起来才更安全,这样就算据库密码泄漏也都是密文。本文分析对应的版本是 5.14。
Spring Security 为我们提供了一套加密规则和密码比对规则,org.springframework.security.crypto.password.PasswordEncoder 接口,该接口里面定义了三个方法。
public interface PasswordEncoder {
//加密(一般在注册的时候加密前端传过来的密码保存进数据库)
String encode(CharSequence rawPassword);
//加密前后对比(一般用来比对前端提交过来的密码和数据库存储密码, 也就是明文和密文的对比,我们只需提供对应对加密类 比对会自行处理)
boolean matches(CharSequence rawPassword, String encodedPassword);
//是否需要再次进行编码, 默认不需要
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
该接口实现类有下面这几个
常用的
- BCryptPasswordEncoder:Spring Security 推荐使用的,使用BCrypt强哈希方法来加密。
- MessageDigestPasswordEncoder:用作传统的加密方式加密(支持 MD5、SHA-1、SHA-256…)
- DelegatingPasswordEncoder:最常用的,根据加密类型id进行不同方式的加密,兼容性强
- NoOpPasswordEncoder:明文, 不做加密
- 其他
使用示例
MessageDigestPasswordEncoder
构造的时候需要传入算法字符串,例如 “MD5”、”SHA-1”、”SHA-256”…
String password = "123";
MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder("MD5");
String encode = encoder.encode(password);
System.out.println(encode);
System.out.println(encoder.matches(password,encode) == true ? "相等" : "不相等");
输出
{EUjIxnT/OVlk5J54s3LaJRuQgwTchm1gduFHTqI0qjo=}4b40375c57c285cc56c7048bb114db23
相等
调用 encode(..)
加密方法每次都会随机生成盐值,所以对相同的明文进行多次加密,每次结果也是不一样的。
从上面输出部分结合源码可以的出:加密的最终结果分为两部分,盐值 + MD5(password+盐值),调用 matches(..)
方法的时候先从密文中得到盐值,用该盐值加密明文和最终密文作对比。
BCryptPasswordEncoder
构造的时候可以传入哈希强度(strength
),强度越大计算量就越大,也就意味着越安全,strength
取值区间[4-31],系统默认是10。
String password = "123";
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encode = encoder.encode(password);
System.out.println(encode);
System.out.println(encoder.matches(password, encode) == true ? "相等" : "不相等");
输出
$2a$10$lxPfE.Zvat6tejB8Q1QGYu3M9lXUUpiWFYzboeyK64kbfgN9v7iBq
相等
调用 encode(..)
方法加密跟上面一样,每次都会随机生成盐值,密文也分为两部分,盐值和最终加密的结果,最终对比的时候从密文里面拿出盐值对明文进行加密,比较最终加密后的结果。
DelegatingPasswordEncoder
这是 Spring Security 推出的一套兼容方案,根据加密类型id字符串(idForEncode
)去自身缓存的所有加密方式中(idToPasswordEncoder
)取出对应的加密方案对象对明文进行加密和对应密文的对比,只是其密文前面都加上了加密方案id的字符串,具体的咱们看下面代码演示。
其初始化 Spring Security 提供了一个工厂构造方法
public class PasswordEncoderFactories {
@SuppressWarnings("deprecation")
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
}
这个工厂的静态构造方法把常用的几种方案都注入到缓存中,但是注入的 idForEncode
对应的却是 BCryptPasswordEncoder
,这样系统就可以达到在新存储密码可以使用 BCryptPasswordEncoder
加密方案进行加密,但是对于数据库里面以前用其他方式加密的密码也支持比对。
String password = "123";
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String encode = encoder.encode(password);
System.out.println(encode);
System.out.println(encoder.matches(password, encode) == true ? "相等" : "不相等");
输出
{bcrypt}$2a$10$Bh23zGZ2YPOsORNexoowb.fX4QH18GEh13eVtZUZvbe2Blx0jIVna
相等
从结果中可以看出,相比原始的 BCryptPasswordEncoder
密文前面多了加密方式的id。
当然也可以自定义构造方法,来制定 DelegatingPasswordEncoder
用其他的方案进行加密。
接下来我们将其指定使用 MD5 方式来加密密码看看结果
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
DelegatingPasswordEncoder encoder = new DelegatingPasswordEncoder("MD5", encoders);
String encode = encoder.encode(password);
System.out.println(encode);
System.out.println(encoder.matches(password, encode) == true ? "相等" : "不相等");
输出
{MD5}{XYwuzP8/lL/a3ASzA9UVM4rFs8lbsLvEoa5ydKER844=}d7f919bfd94554150f8ab3a809209ee3
相等
相比原始的 MessageDigestPasswordEncoder
也是密文前面多了加密方式的id。
如果我们想同时使用多个密码加密方案,看来使用 DelegatingPasswordEncoder 就可以了,而 DelegatingPasswordEncoder 默认还不用配置。
首先我们来生成三个密码作为测试密码:
@Test
void contextLoads() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
DelegatingPasswordEncoder encoder1 = new DelegatingPasswordEncoder("bcrypt", encoders);
DelegatingPasswordEncoder encoder2 = new DelegatingPasswordEncoder("MD5", encoders);
DelegatingPasswordEncoder encoder3 = new DelegatingPasswordEncoder("noop", encoders);
String e1 = encoder1.encode("123");
String e2 = encoder2.encode("123");
String e3 = encoder3.encode("123");
System.out.println("e1 = " + e1);
System.out.println("e2 = " + e2);
System.out.println("e3 = " + e3);
}
结果
e1 = {bcrypt}$2a$10$Sb1gAUH4wwazfNiqflKZve4Ubh.spJcxgHG8Cp29DeGya5zsHENqi
e2 = {MD5}{Wucj/L8wMTMzFi3oBKWsETNeXbMFaHZW9vCK9mahMHc=}4d43db282b36d7f0421498fdc693f2a2
e3 = {noop}123
接下来,我们把这三个密码拷贝到 SecurityConfig 中去:
@Configuration("aaa")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin").password("{bcrypt}$2a$10$Sb1gAUH4wwazfNiqflKZve4Ubh.spJcxgHG8Cp29DeGya5zsHENqi").roles("admin").build());
manager.createUser(User.withUsername("admintwo").password("{noop}123").roles("admin").build());
manager.createUser(User.withUsername("dminthree").password("{MD5}{Wucj/L8wMTMzFi3oBKWsETNeXbMFaHZW9vCK9mahMHc=}4d43db282b36d7f0421498fdc693f2a2").roles("user").build());
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
...
}
}
这里三个用户使用三种不同的密码加密方式。
配置完成后,重启项目,分别使用 admin/123、admintwo/123 以及 dminthree/123 进行登录,发现都能登录成功。
为什么我们会有这种需求?想在项目种同时存在多种密码加密方案?其实这个主要是针对老旧项目改造用的,密码加密方式一旦确定,基本上没法再改了(你总不能让用户重新注册一次吧),但是我们又想使用最新的框架来做密码加密,那么无疑,DelegatingPasswordEncoder 是最佳选择。
应用
对于我们开发者而言,我们通常都是在 SecurityConfig 中配置一个 PasswordEncoder 的实例,类似下面这样:
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
剩下的事情,都是由系统调用的
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("liuchao")
.password("{bcrypt}$2a$10$S.hMD3oV60YRIj38lHRhP.e3DAu3OwmssE/u/p2GLqqZ3SVsZA77W")
.roles("admin","user")
.and()
.passwordEncoder(passwordEncoder);
}
@Bean(value = "passwordEncoder")
public PasswordEncoder delegatingPasswordEncoder() {
//构造 DelegatingPasswordEncoder 加密方案
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Autowired
private PasswordEncoder passwordEncoder;
}
五、授权
上面进行认证登录的同时指定了一些角色,以便授权,授权操作在Spring Security 的 configure(HttpSecurity http) 方法中进行,我们需要在配置类中重写如下两个方法,并进行相应的授权
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**","/images/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")//只有admin角色才能访问admin地址
.antMatchers("/user/**").hasRole("user")//只有user角色才能访问/user地址
.anyRequest().authenticated();
}
这里的匹配规则我们采用了 Ant 风格的路径匹配符,Ant 风格的路径匹配符在 Spring 家族中使用非常广泛,它的匹配规则也非常简单:
通配符 | 含义 |
* | 匹配多层路径 |
匹配一层路径 | |
? | 匹配任意单个字符 |
上面配置的含义是:
- 如果请求路径满足
/admin/**
格式,则用户需要具备 admin 角色。 - 如果请求路径满足
/user/**
格式,则用户需要具备 user 角色。 - 剩余的其他格式的请求路径,只需要认证(登录)后就可以访问。
注意代码中配置的三条规则的顺序非常重要,和 Shiro 类似,Spring Security 在匹配的时候也是按照从上往下的顺序来匹配,一旦匹配到了就不继续匹配了,所以拦截规则的顺序不能写错。另一方面,须将 anyRequest 配置在 antMatchers 前面
角色继承
要实现所有 user 能够访问的资源,admin 都能够访问,这涉及到另外一个知识点,叫做角色继承。
这在实际开发中非常有用。上级可能具备下级的所有权限,如果使用角色继承,这个功能就很好实现,我们只需要在 SecurityConfig 中添加如下代码来配置角色继承关系即可:
@Bean
RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_admin > ROLE_user");
return hierarchy;
}
注意,在配置时,需要给角色手动加上 ROLE_
前缀。上面的配置表示 ROLE_admin
自动具备 ROLE_user
的权限。
还没有评论,来说两句吧...