id |
authority |
description |
---|---|---|
1 |
ROLEADMINUSER |
管理全部用戶 |
2 |
ROLEADMINRESOURCE | 管理全部資源 |
3 |
ROLEA1 |
訪問 ServiceA 的某接口的權限 |
4 |
ROLEA2 |
訪問 ServiceA 的另外一個接口的權限 |
5 |
ROLEB1 |
訪問 ServiceB 的某接口的權限 |
6 |
ROLEB2 |
訪問 ServiceB 的另外一個接口的權限 |
id |
name |
authority_ids |
---|---|---|
1 |
超級管理員 | 1,2,3,4,5,6 |
2 |
管理員A |
3,4 |
3 |
管理員B |
5,6 |
4 |
普通用戶 |
NULL |
user_id | role_id |
---|---|
1 |
1 |
1 |
4 |
2 |
2 |
Maven 依賴(全部服務)前端
<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Session Redis -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>複製代碼
應用配置 application.yml
示例:mysql
# Spring Session 配置
spring.session.store-type=redis
server.servlet.session.persistent=true
server.servlet.session.timeout=7d
server.servlet.session.cookie.max-age=7d
# Redis 配置
spring.redis.host=<redis-host>
spring.redis.port=6379
# MySQL 配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://<mysql-host>:3306/test
spring.datasource.username=<username>
spring.datasource.password=<passowrd>複製代碼
用戶登陸認證(authentication)與受權(authority)面試
Slf4j
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final UserService userService;
CustomAuthenticationFilter(String defaultFilterProcessesUrl, UserService userService) {
super(new AntPathRequestMatcher(defaultFilterProcessesUrl, HttpMethod.POST.name()));
this.userService = userService;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
JSONObject requestBody = getRequestBody(request);
String username = requestBody.getString("username");
String password = requestBody.getString("password");
UserDO user = userService.getByUsername(username);
if (user != null && validateUsernameAndPassword(username, password, user)){
// 查詢用戶的 authority
List<SimpleGrantedAuthority> userAuthorities = userService.getSimpleGrantedAuthority(user.getId());
return new UsernamePasswordAuthenticationToken(user.getId(), null, userAuthorities);
}
throw new AuthenticationServiceException("登陸失敗");
}
/**
* 獲取請求體
*/
private JSONObject getRequestBody(HttpServletRequest request) throws AuthenticationException{
try {
StringBuilder stringBuilder = new StringBuilder();
InputStream inputStream = request.getInputStream();
byte[] bs = new byte[StreamUtils.BUFFER_SIZE];
int len;
while ((len = inputStream.read(bs)) != -1) {
stringBuilder.append(new String(bs, 0, len));
}
return JSON.parseObject(stringBuilder.toString());
} catch (IOException e) {
log.error("get request body error.");
}
throw new AuthenticationServiceException(HttpRequestStatusEnum.INVALID_REQUEST.getMessage());
}
/**
* 校驗用戶名和密碼
*/
private boolean validateUsernameAndPassword(String username, String password, UserDO user) throws AuthenticationException {
return username == user.getUsername() && password == user.getPassword();
}
}
@EnableWebSecurity
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String LOGIN_URL = "/user/login";
private static final String LOGOUT_URL = "/user/logout";
private final UserService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(LOGIN_URL).permitAll()
.anyRequest().authenticated()
.and()
.logout().logoutUrl(LOGOUT_URL).clearAuthentication(true).permitAll()
.and()
.csrf().disable();
http.addFilterAt(bipAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.rememberMe().alwaysRemember(true);
}
/**
* 自定義認證過濾器
*/
private CustomAuthenticationFilter customAuthenticationFilter() {
CustomAuthenticationFilter authenticationFilter = new CustomAuthenticationFilter(LOGIN_URL, userService);
return authenticationFilter;
}
}
複製代碼
應用配置 application.yml
示例:redis
# Spring Session 配置
spring.session.store-type=redis
# Redis 配置
spring.redis.host=<redis-host>
spring.redis.port=6379複製代碼
全局安全配置spring
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
}複製代碼
用戶經過用戶服務登陸成功後,用戶信息會被緩存到 Redis,緩存的信息與 CustomAuthenticationFilter
中 attemptAuthentication()
方法返回的對象有關,如上因此,返回的對象是 new UsernamePasswordAuthenticationToken(user.getId(), null, userAuthorities)
,即 Redis 緩存了用戶的 ID 和用戶的權力(authorities)。sql
UsernamePasswordAuthenticationToken
構造函數的第一個參數是 Object 對象,因此能夠自定義緩存對象。json
在微服務各個模塊獲取用戶的這些信息的方法以下:緩存
@GetMapping()
public WebResponse test(@AuthenticationPrincipal UsernamePasswordAuthenticationToken authenticationToken){
// 略
}複製代碼
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}複製代碼
ROLE_ADMIN_USER
權限的用戶訪問。 /**
* 刪除角色
*/
@PostMapping("/delete")
@PreAuthorize("hasRole('ADMIN_USER')")
public WebResponse deleteRole(@RequestBody RoleBean roleBean){
// 略
}複製代碼
@PreAuthorize("hasRole('
可做用於微服務中的各個模塊安全')")
hasRole()
方法是 Spring Security 內嵌的,如需自定義,可使用 Expression-Based Access Control,示例: /**
* 自定義校驗服務
*/
@Service
public class CustomService{
public boolean check(UsernamePasswordAuthenticationToken authenticationToken, String extraParam){
// 略
}
}
/**
* 刪除角色
*/
@PostMapping()
@PreAuthorize("@customService.check(authentication, #userBean.username)")
public WebResponse custom(@RequestBody UserBean userBean){
// 略
}
複製代碼
authentication
屬於內置對象,#
獲取入參的值cookie
@Service
@AllArgsConstructor
public class HttpSessionService<S extends Session> {
private final FindByIndexNameSessionRepository<S> sessionRepository;
/**
* 重置用戶權限
*/
public void resetAuthorities(Long userId, List<GrantedAuthority> authorities){
UsernamePasswordAuthenticationToken newToken = new UsernamePasswordAuthenticationToken(userId, null, authorities);
Map<String, S> redisSessionMap = sessionRepository.findByPrincipalName(String.valueOf(userId));
redisSessionMap.values().forEach(session -> {
SecurityContextImpl securityContext = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
securityContext.setAuthentication(newToken);
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext);
sessionRepository.save(session);
});
}
}複製代碼
修改用戶權限,僅需調用 httpSessionService.resetAuthorities()
方法便可,實時生效。
© 著做權歸做者全部,轉載或內容合做請聯繫做者
● APM工具尋找了一圈,發現SkyWalking纔是個人真愛
● Spring Boot 注入外部配置到應用內部的靜態變量
● Java 使用 UnixSocket 調用 Docker API
● Service Mesh - gRPC 本地聯調遠程服務
● Spring Security 實戰乾貨:如何保護用戶密碼
● Spring Boot RabbitMQ - 優先級隊列
原文連接:https://mp.weixin.qq.com/s?__biz=MzU0MDEwMjgwNA==&mid=2247486167&idx=2&sn=76dba01d16b7147c9b1dfb7cbf2d8d28&chksm=fb3f132ccc489a3ad2ea05314823d660c40e8af90dcd35800422899958f98b4a258d23badba8&token=280305379&lang=zh_CN#rd
本文由博客一文多發平臺 OpenWrite 發佈!