Spring Security + jwt 許可權系統設計,包含SQL
完整專案程式碼
https://download.csdn.net/download/y1534414425/13123475
引言
這是一個Spring Security + jwt 做的一個許可權系統設計的demo,註冊預設是使用者角色,沒有登入的情況下不可以訪問使用者和管理員介面,每個角色擁有訪問指定路徑下的介面,管理員許可權繼承自使用者,所以管理員擁有使用者的所有許可權,使用者訪問不了管理員介面,。
一、資料設計
二、關鍵部分程式碼
Spring Security主要配置SecurityConfig
package com.springsecurity.security.config;
import com.springsecurity.security.exception.JwtAccessDeniedHandler;
import com.springsecurity.security.exception.JwtAuthenticationEntryPoint;
import com.springsecurity.security.filter.JwtAuthenticationFilter;
import com.springsecurity.security.filter.JwtAuthorizationFilter;
import com.springsecurity.security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
/**
* 密碼編碼器
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 設定自定義的userDetailsService以及密碼編碼器
auth.userDetailsService(userDetailsServiceImpl)
// 配置密碼加密規則
.passwordEncoder(bCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
// 禁用 CSRF
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/auth/login").permitAll()
// 指定路徑下的資源需要管理員許可權才能訪問
.antMatchers("/user/admin/**").hasRole("ADMIN")
// 指定路徑下的資源需要使用者許可權才能訪問
.antMatchers("/user/*").hasRole("USER")
// 其他都放行了
.anyRequest().permitAll()
.and()
//新增自定義Filter
.addFilter(new JwtAuthenticationFilter(authenticationManager())) // 認證
.addFilter(new JwtAuthorizationFilter(authenticationManager(), userDetailsServiceImpl)) // 授權
// 不需要session(不建立會話)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 授權異常處理
.exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new JwtAccessDeniedHandler())
.and()
.formLogin().loginProcessingUrl("/login").permitAll();
// 防止 web 頁面的Frame 被攔截
http.headers().frameOptions().disable();
}
/**
* 角色繼承
*
* @return
*/
@Bean
RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_ADMIN > ROLE_USER";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
}
認證過濾器
package com.springsecurity.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springsecurity.exception.LoginFailedException;
import com.springsecurity.security.constants.SecurityConstants;
import com.springsecurity.security.dto.LoginRequest;
import com.springsecurity.security.entity.JwtUser;
import com.springsecurity.security.utils.JwtTokenUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
/**
* 如果使用者名稱和密碼正確,那麼過濾器將建立一個JWT Token 並在HTTP Response 的header中返回它,格式:token: "Bearer +具體token值"
*/
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final ThreadLocal<Boolean> rememberMe = new ThreadLocal<>();
private final AuthenticationManager authenticationManager;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
// 設定URL,以確定是否需要身份驗證
super.setFilterProcessesUrl(SecurityConstants.AUTH_LOGIN_URL);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 獲取登入的資訊
LoginRequest loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequest.class);
rememberMe.set(loginRequest.getRememberMe());
// 這部分和attemptAuthentication方法中的原始碼是一樣的,
// 只不過由於這個方法原始碼的是把使用者名稱和密碼這些引數的名字是死的,所以我們重寫了一下
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(), loginRequest.getPassword());
return authenticationManager.authenticate(authentication);
} catch (IOException | AuthenticationException e) {
if (e instanceof AuthenticationException) {
throw new LoginFailedException("登入失敗!請檢查使用者名稱和密碼。");
}
throw new LoginFailedException(e.getMessage());
}
}
/**
* 如果驗證成功,就生成token並返回
*/
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authentication) {
JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
List<String> authorities = jwtUser.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
// 建立 Token
String token = JwtTokenUtils.createToken(jwtUser.getUsername(), authorities, rememberMe.get());
rememberMe.remove();
// Http Response Header 中返回 Token
response.setHeader(SecurityConstants.TOKEN_HEADER, token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}
授權過濾器
package com.springsecurity.security.filter;
import com.springsecurity.exception.UserNameNotFoundException;
import com.springsecurity.security.constants.SecurityConstants;
import com.springsecurity.security.service.UserDetailsServiceImpl;
import com.springsecurity.security.utils.JwtTokenUtils;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.security.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.logging.Logger;
/**
* 過濾器處理所有HTTP請求,並檢查是否存在帶有正確令牌的Authorization標頭。例如,如果令牌未過期或簽名金鑰正確。
*/
@Slf4j
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private final UserDetailsServiceImpl userDetailsService;
private static final Logger logger = Logger.getLogger(JwtAuthorizationFilter.class.getName());
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserDetailsServiceImpl userDetailsService) {
super(authenticationManager);
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String token = request.getHeader(SecurityConstants.TOKEN_HEADER);
if (token == null || !token.startsWith(SecurityConstants.TOKEN_PREFIX)) {
SecurityContextHolder.clearContext();
} else {
UsernamePasswordAuthenticationToken authentication = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
/**
* 獲取使用者認證資訊 Authentication
*/
private UsernamePasswordAuthenticationToken getAuthentication(String authorization) {
log.info("get authentication");
String token = authorization.replace(SecurityConstants.TOKEN_PREFIX, "");
try {
String username = JwtTokenUtils.getUsernameByToken(token);
logger.info("checking username:" + username);
if (!StringUtils.isEmpty(username)) {
// 這裡我們是又從資料庫拿了一遍,避免使用者的角色資訊有變
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, null, userDetails.getAuthorities());
return userDetails.isEnabled() ? usernamePasswordAuthenticationToken : null;
}
} catch (UserNameNotFoundException | SignatureException | ExpiredJwtException | MalformedJwtException | IllegalArgumentException exception) {
logger.warning("Request to parse JWT with invalid signature . Detail : " + exception.getMessage());
}
return null;
}
}
三、效果
使用者登入
未登入訪問使用者介面
使用者訪問使用者介面
使用者訪問管理員介面
管理員登入
未登入訪問管理員介面
管理員訪問使用者介面
管理員訪問管理員介面
相關文章
- 基於Spring Security和 JWT的許可權系統設計SpringJWT
- 許可權系統設計
- 基於Spring Security實現許可權管理系統Spring
- Spring security(五)-完美許可權管理系統(授權過程分析)Spring
- spring security許可權認證Spring
- 許可權系統:許可權應用服務設計
- SpringBoot整合Spring security JWT實現介面許可權認證Spring BootJWT
- 許可權系統:6個許可權概念模型設計模型
- 許可權系統:許可權應用服務設計Tu
- 檢視角色裡包含的系統許可權、物件許可權和角色物件
- 許可權系統設計(2)--operation
- 許可權系統設計(3)-- subject
- 許可權系統設計(4)--resource
- 許可權系統設計--概論
- 使用者許可權設計(三)——通用資料許可權管理系統設計
- 關於許可權系統的設計
- Spring Security實現統一登入與許可權控制Spring
- Oracle的物件許可權、角色許可權、系統許可權Oracle物件
- 續:關於許可權系統的設計
- 許可權系統設計(1)--基本模式模式
- 許可權系統設計(5)--動態性
- 系統許可權資料庫設計方案資料庫
- Security 10:許可權管理
- 基於 Spring Security 的前後端分離的許可權控制系統Spring後端
- Spring Security 基於URL的許可權判斷Spring
- 分散式系統中,許可權設計實踐分散式
- 關於系統許可權的設計-位操作
- 基於Spring Security Oauth2的SSO單點登入+JWT許可權控制實踐SpringOAuthJWT
- Android系統許可權和root許可權Android
- 許可權系統:一文搞懂功能許可權、資料許可權
- Spring Security原始碼分析十三:Spring Security 基於表示式的許可權控制Spring原始碼
- 適配懸浮窗許可權與系統設定修改許可權
- MySQL許可權系統MySql
- Oracle系統許可權Oracle
- SpringSecurity許可權管理系統實戰—六、SpringSecurity整合JWTSpringGseJWT
- 手把手擼套框架-許可權系統設計框架
- learun通用許可權系統框架功能實現設計框架
- 管理系統之許可權的設計和實現