1、功能实现
创建内存用户,进行登录
访问权限接口
2、security01 子工程
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.yzm</groupId>
<artifactId>security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>security01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>security01</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>com.yzm</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
由于还没有用到数据库,启动类 exclude 数据库自动配置,以免启动报错
package com.yzm.security01;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class Security01Application {
public static void main(String[] args) {
SpringApplication.run(Security01Application.class, args);
}
}
3、SecurityConfig 配置类
package com.yzm.security01.config;
import lombok.extern.slf4j.Slf4j;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Slf4j
@Configuration
@EnableWebSecurity // 开启 Security 服务
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 密码编码器
* passwordEncoder.encode是用来加密的,passwordEncoder.matches是用来解密的
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 配置用户,这里是创建内存用户
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 从内存创建用户
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123456"))
// 基于内存创建的用户不能同时使用roles和authorities,如果同时使用只有后面的生效,这个坑
.roles("ADMIN", "USER")
//.authorities("select", "delete")
.and()
.withUser("yzm")
.password(passwordEncoder().encode("123456"))
.roles("USER")
//.authorities("select")
;
}
/**
* http安全配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭CSRF跨域
.csrf().disable()
// 默认登录
.formLogin().permitAll()
.and()
// 退出登录
.logout().permitAll()
.and()
// 访问路径URL的授权策略,如注册、登录免登录认证等
.authorizeRequests()
.antMatchers("/home", "/").permitAll() //指定url放行
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER") // 需要角色(二选一)
.antMatchers("/admin/**").hasRole("ADMIN") // 需要角色
.anyRequest().authenticated() //其他任何请求都需要身份认证
.and()
;
}
}
4、访问接口
package com.yzm.security01.controller;
import com.alibaba.fastjson.JSONObject;
import com.yzm.common.entity.HttpResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
@GetMapping(value = {"/", "/home"})
public Object home() {
return "home";
}
@GetMapping("/hello")
@ResponseBody
public Object hello() {
return "hello";
}
// 通过authentication或userDetails获取当前登录用户信息+
@GetMapping(value = {"/user", "/admin"})
@ResponseBody
public String info(Authentication authentication, @AuthenticationPrincipal UserDetails userDetails) {
System.out.println("authentication :");
System.out.println(JSONObject.toJSONString(authentication, true));
System.out.println("userDetails :");
System.out.println(JSONObject.toJSONString(userDetails, true));
return "请求成功";
}
@GetMapping(value = {"/user/select", "/admin/select"})
@ResponseBody
public Object select() {
return "Select";
}
@GetMapping(value = {"/user/create", "/admin/create"})
@ResponseBody
public Object create() {
return "Create";
}
@GetMapping(value = {"/user/update", "/admin/update"})
@ResponseBody
public Object update() {
return "Update";
}
@GetMapping(value = {"/user/delete", "/admin/delete"})
@ResponseBody
public Object delete() {
return "Delete";
}
}
5、首页,访问链接,便于测试
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h2>
<a href="/hello"> hello </a>
</h2>
<h3>user</h3>
<h4>
<a href="/user">User角色</a>
</h4>
<p><a href="/user/select">User角色,拥有 select 权限</a></p>
<p><a href="/user/create">User角色,拥有 create 权限</a></p>
<p><a href="/user/update">User角色,拥有 update 权限</a></p>
<p><a href="/user/delete">User角色,拥有 delete 权限</a></p>
<h3>admin</h3>
<h4>
<a href="/admin">Admin角色</a>
</h4>
<p><a href="/admin/select">Admin角色,拥有 select 权限</a></p>
<p><a href="/admin/create">Admin角色,拥有 create 权限</a></p>
<p><a href="/admin/update">Admin角色,拥有 update 权限</a></p>
<p><a href="/admin/delete">Admin角色,拥有 delete 权限</a></p>
</body>
</html>
6、测试 roles 权限
拥有用户:
admin --> ADMIN、USER
yzm --> USER
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 从内存创建用户
auth.inMemoryAuthentication()
.withUser("admin")
// 密码需要加密,不加就提示错误
.password(passwordEncoder().encode("123456"))
// 基于内存创建的用户不能同时使用roles和authorities,如果同时使用只有后面的生效,这个坑
.roles("ADMIN", "USER")
//.authorities("select", "delete")
.and()
.withUser("yzm")
.password(passwordEncoder().encode("123456"))
.roles("USER")
//.authorities("select")
;
}
启动项目 访问 / 或 /home 首页接口,由于是放行的,所以可以访问
点击 hello 跳转到Security默认提供的登录页面
.authorizeRequests()
.antMatchers("/home", "/").permitAll() //指定url放行
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER") // 需要角色(二选一)
.antMatchers("/admin/**").hasRole("ADMIN") // 需要角色
.anyRequest().authenticated() //其他任何请求都需要身份认证
.and()
在前面我们没有拦截/hello,它走的是需要认证 即.anyRequest().authenticated()
没有认证的请求,默认转发到 /login
开始登录yzm
登录成功之后,继续之前的 /hello 请求
如果我们创建内存用户不是使用加密的,而是直接明文
把.password(passwordEncoder().encode(“123456”)) 改成 .password(“123456”)
那么我们点击登录,就会提示下面的错误
提示 Encoded password does not look like BCrypt (密码没有加密)
回到首页,此时登录用户:yzm,能访问部分接口
点击 user角色 可以查看当前登录的用户信息
authentication :
{
"authenticated":true,
"authorities":[
{
"authority":"user:select"
}
],
"details":{
"remoteAddress":"0:0:0:0:0:0:0:1",
"sessionId":"20A8546453B14E0C3B066A8150B44B9D"
},
"name":"yzm",
"principal":{
"accountNonExpired":true,
"accountNonLocked":true,
"authorities":[{"$ref":"$.authorities[0]"}],
"credentialsNonExpired":true,
"enabled":true,
"username":"yzm"
}
}
userDetails :
{
"accountNonExpired":true,
"accountNonLocked":true,
"authorities":[{
"authority":"user:select"
}],
"credentialsNonExpired":true,
"enabled":true,
"username":"yzm"
}
Authentication 用户认证对象 ,内容比较多并且包含了 UserDetails信息
UserDetails 用户详情对象
访问 localhost:8080/logout 退出登录
登录admin,admin有双重身份,可以访问所有的接口
7、测试 authorities 权限
admin --> admin:select、admin:delete
yzm --> user:select
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123456"))
// 基于内存创建的用户不能同时使用roles和authorities,如果同时使用只有后面的生效,这个坑
//.roles("ADMIN", "USER")
.authorities("admin:select", "admin:delete")
.and()
.withUser("yzm")
.password(passwordEncoder().encode("123456"))
//.roles("USER")
.authorities("user:select")
;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.antMatchers("/user/select").hasAuthority("user:select") // 需要权限
.antMatchers("/user/delete").hasAuthority("user:delete")
.antMatchers("/admin/select").hasAuthority("admin:select")
.antMatchers("/admin/delete").hasAnyAuthority("admin:delete", "admin:remove") // 需要权限(二选一)
.anyRequest().authenticated() //其他任何请求都需要身份认证
.and()
;
}
重启项目
8、问题
.withUser("admin")
// 密码需要加密,不加就提示错误
.password(passwordEncoder().encode("123456"))
// 基于内存创建的用户不能同时使用roles和authorities,如果同时使用只有后面的生效,这个坑
//.roles("ADMIN", "USER")
.authorities("admin:select", "admin:delete")
在上面的代码中 roles() 跟 authorities() 能不能同时设置?
可以同时设置,但只有最后设置的那个有效,后面设置的会替换掉前面设置的
我们可以看下它们的实现
public UserDetailsManagerConfigurer<B, C>.UserDetailsBuilder roles(String... roles) {
this.user.roles(roles);
return this;
}
public UserDetailsManagerConfigurer<B, C>.UserDetailsBuilder authorities(GrantedAuthority... authorities) {
this.user.authorities(authorities);
return this;
}
public UserBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList<>(roles.length);
for (String role : roles) {
Assert.isTrue(!role.startsWith("ROLE_"),
() -> role + " cannot start with ROLE_ (it is automatically added)");
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
// roles最后是调用authorities()方法的
return authorities(authorities);
}
public UserBuilder authorities(GrantedAuthority... authorities) {
return authorities(Arrays.asList(authorities));
}
public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = new ArrayList<>(authorities);
return this;
}
可以看到roles和authorities最后都是给authorities赋值,使用户拥有对应的角色权限
所以我们一般设置一个就可以了