SpringBoot3安全管理操作方法
一、簡(jiǎn)介
SpringSecurity組件可以為服務(wù)提供安全管理的能力,比如身份驗(yàn)證、授權(quán)和針對(duì)常見攻擊的保護(hù),是保護(hù)基于spring應(yīng)用程序的事實(shí)上的標(biāo)準(zhǔn);
在實(shí)際開發(fā)中,最常用的是登錄驗(yàn)證和權(quán)限體系兩大功能,在登錄時(shí)完成身份的驗(yàn)證,加載相關(guān)信息和角色權(quán)限,在訪問其他系統(tǒng)資源時(shí),進(jìn)行權(quán)限的驗(yàn)證,保護(hù)系統(tǒng)的安全;
二、工程搭建
1、工程結(jié)構(gòu)

2、依賴管理
在 starter-security 依賴中,實(shí)際上是依賴 spring-security 組件的 6.1.1 版本,對(duì)于該框架的使用,主要是通過自定義配置類進(jìn)行控制;
<!-- 安全組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot.version}</version>
</dependency>三、配置管理
1、核心配置類
在該類中涉及到的配置非常多,主要是服務(wù)的攔截控制,身份認(rèn)證的處理流程以及過濾器等,很多自定義的處理類通過該配置進(jìn)行加載;
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
/**
* 基礎(chǔ)配置
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
// 配置攔截規(guī)則
httpSecurity.authorizeHttpRequests(authorizeHttpRequests->{
authorizeHttpRequests
.requestMatchers(WhiteConfig.whiteList()).permitAll()
.anyRequest().authenticated();
});
// 禁用默認(rèn)的登錄和退出
httpSecurity.formLogin(AbstractHttpConfigurer::disable);
httpSecurity.logout(AbstractHttpConfigurer::disable);
httpSecurity.csrf(AbstractHttpConfigurer::disable);
// 異常時(shí)認(rèn)證處理流程
httpSecurity.exceptionHandling(exeConfig -> {
exeConfig.authenticationEntryPoint(authenticationEntryPoint());
});
// 添加過濾器
httpSecurity.addFilterAt(authTokenFilter(),CsrfFilter.class);
return httpSecurity.build() ;
}
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthExeHandler();
}
@Bean
public OncePerRequestFilter authTokenFilter () {
return new AuthTokenFilter();
}
/**
* 認(rèn)證管理
*/
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(authenticationProvider()) ;
}
/**
* 自定義用戶認(rèn)證流
*/
@Bean
public AbstractUserDetailsAuthenticationProvider authenticationProvider() {
return new AuthProvider() ;
}
}2、認(rèn)證數(shù)據(jù)源
UserDetailsService 是加載用戶特定數(shù)據(jù)的核心接口,編寫用戶服務(wù)類并實(shí)現(xiàn)該接口,提供用戶信息和權(quán)限體系的數(shù)據(jù)查詢和加載,作為用戶身份識(shí)別的關(guān)鍵憑據(jù);
@Service
public class UserService implements UserDetailsService {
@Resource
private UserBaseMapper userBaseMapper;
@Resource
private BCryptPasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
UserBase queryUser = geyByUserName(userName);
if (Objects.isNull(queryUser)){
throw new AuthException("該用戶不存在");
}
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>() ;
grantedAuthorityList.add(new SimpleGrantedAuthority(queryUser.getUserRole())) ;
return new User(queryUser.getUserName(),queryUser.getPassWord(),grantedAuthorityList);
}
public int register (UserBase userBase){
if (!Objects.isNull(userBase)){
userBase.setPassWord(passwordEncoder.encode(userBase.getPassWord()));
userBase.setCreateTime(new Date()) ;
return userBaseMapper.insert(userBase) ;
}
return 0 ;
}
public UserBase getById (Integer id){
return userBaseMapper.selectById(id) ;
}
public UserBase geyByUserName (String userName){
List<UserBase> userBaseList = new LambdaQueryChainWrapper<>(userBaseMapper)
.eq(UserBase::getUserName,userName).last("limit 1").list();
if (userBaseList.size() > 0){
return userBaseList.get(0) ;
}
return null ;
}
}3、認(rèn)證流程
自定義用戶名和密碼的身份令牌認(rèn)證邏輯,基于用戶名 Username 從上面的用戶服務(wù)類中加載數(shù)據(jù)并校驗(yàn),在驗(yàn)證成功后將用戶的身份令牌返回給調(diào)用者;
@Component
public class AuthProvider extends AbstractUserDetailsAuthenticationProvider {
private static final Logger log = LoggerFactory.getLogger(AuthProvider.class);
@Resource
private UserService userService;
@Resource
private BCryptPasswordEncoder passwordEncoder;
@Override
protected void additionalAuthenticationChecks(
UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
User user = (User) userDetails;
String loginPassword = authentication.getCredentials().toString();
log.info("user:{},loginPassword:{}",user.getPassword(),loginPassword);
if (!passwordEncoder.matches(loginPassword, user.getPassword())) {
throw new AuthException("賬號(hào)或密碼錯(cuò)誤");
}
authentication.setDetails(user);
}
@Override
protected UserDetails retrieveUser(
String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
log.info("username:{}",username);
return userService.loadUserByUsername(username);
}
}4、身份過濾器
通過繼承 OncePerRequestFilter 抽象類,實(shí)現(xiàn)用戶身份的過濾器,如果不是白名單請(qǐng)求,需要驗(yàn)證令牌是否正確有效, SecurityContextHolder 默認(rèn)狀態(tài)下使用 ThreadLocal 存儲(chǔ)信息;
@Component
public class AuthTokenFilter extends OncePerRequestFilter {
@Resource
private AuthTokenService authTokenService ;
@Resource
private AuthExeHandler authExeHandler ;
@Override
protected void doFilterInternal(@Nonnull HttpServletRequest request,
@Nonnull HttpServletResponse response,
@Nonnull FilterChain filterChain) throws ServletException, IOException {
String uri = request.getRequestURI();
if (Arrays.asList(WhiteConfig.whiteList()).contains(uri)){
// 如果是白名單直接放行
filterChain.doFilter(request,response);
} else {
String token = request.getHeader("Auth-Token");
if (Objects.isNull(token) || token.isEmpty()){
// Token不存在,攔截返回
authExeHandler.commence(request,response,null);
} else {
Object object = authTokenService.getToken(token);
if (!Objects.isNull(object) && object instanceof User user){
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user, null,user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request,response);
} else {
// Token驗(yàn)證失敗,攔截返回
authExeHandler.commence(request,response,null);
}
}
}
}
}四、核心功能
1、登錄退出
自定義登錄和退出兩個(gè)接口,基于用戶名和密碼執(zhí)行上述的身份認(rèn)證流程,如果認(rèn)證成功則返回用戶的身份令牌,在請(qǐng)求「非」白名單接口時(shí)需要在請(qǐng)求頭中 Auth-Token:token 攜帶該令牌,在退出時(shí)會(huì)清除身份信息;
@Service
public class LoginService {
private static final Logger log = LoggerFactory.getLogger(LoginService.class);
@Resource
private AuthTokenService authTokenService ;
@Resource
private AuthenticationManager authenticationManager;
public String doLogin (UserBase userBase){
AbstractAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userBase.getUserName().trim(), userBase.getPassWord().trim());
Authentication authentication = authenticationManager.authenticate(authToken) ;
User user = (User) authentication.getDetails();
return authTokenService.createToken(user) ;
}
public Boolean doLogout (String authToken){
SecurityContextHolder.clearContext();
return authTokenService.deleteToken(authToken) ;
}
}
@Service
public class AuthTokenService {
private static final Logger log = LoggerFactory.getLogger(AuthTokenService.class);
@Resource
private RedisTemplate<String,Object> redisTemplate ;
public String createToken (User user){
String userName = user.getUsername();
String token = DigestUtils.md5DigestAsHex(userName.getBytes());
log.info("user-name:{},create-token:{}",userName,token);
redisTemplate.opsForValue().set(token,user,10, TimeUnit.MINUTES);
return token ;
}
public Object getToken (String token){
return redisTemplate.opsForValue().get(token);
}
public Boolean deleteToken (String token){
return redisTemplate.delete(token);
}
}2、權(quán)限校驗(yàn)
UserWeb 類中提供用戶的注冊(cè)接口,在用戶表中創(chuàng)建兩個(gè)測(cè)試用戶: admin 對(duì)應(yīng) ROLE_Admin 角色, user 對(duì)應(yīng) ROLE_User 角色,驗(yàn)證如下幾個(gè)接口的權(quán)限控制;
select 接口不需要鑒權(quán),攔截器放行即可訪問; getUser 接口校驗(yàn) ROLE_User 角色; getAdmin 接口校驗(yàn) ROLE_Admin 角色; query 接口校驗(yàn)兩個(gè)角色中的任意一個(gè)即可;
兩個(gè)不同用戶登錄獲取到各自的身份令牌,使用不同的令牌請(qǐng)求接口,在 PreAuthorize 驗(yàn)證通過后才可以正常訪問;
@RestController
public class UserWeb {
@Resource
private UserService userService ;
@PostMapping("/register")
public String register (@RequestBody UserBase userBase){
return "register-"+userService.register(userBase) ;
}
@GetMapping("/select/{id}")
public UserBase select (@PathVariable Integer id){
return userService.getById(id) ;
}
@PreAuthorize("hasRole('User')")
@GetMapping("/user/{id}")
public UserBase getUser (@PathVariable Integer id){
return userService.getById(id) ;
}
@PreAuthorize("hasRole('Admin')")
@GetMapping("/admin/{id}")
public UserBase getAdmin (@PathVariable Integer id){
return userService.getById(id) ;
}
@PreAuthorize("hasAnyRole('User','Admin')")
@GetMapping("/query/{id}")
public UserBase query (@PathVariable Integer id){
return userService.getById(id) ;
}
}五、參考源碼
文檔倉(cāng)庫: https://gitee.com/cicadasmile/butte-java-note 源碼倉(cāng)庫: https://gitee.com/cicadasmile/butte-spring-parent
Gitee主頁:https://gitee.com/cicadasmile/butte-java-note
到此這篇關(guān)于SpringBoot3安全管理操作方法的文章就介紹到這了,更多相關(guān)SpringBoot3安全管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談s:select 標(biāo)簽中l(wèi)ist存放map對(duì)象的使用
下面小編就為大家?guī)硪黄獪\談s:select 標(biāo)簽中l(wèi)ist存放map對(duì)象的使用。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11
java中hasNextInt判斷后無限循環(huán)輸出else項(xiàng)的解決方法
這篇文章主要介紹了java中hasNextInt判斷后無限循環(huán)輸出else項(xiàng)的解決方法的相關(guān)資料,需要的朋友可以參考下2016-10-10
Java編程實(shí)現(xiàn)遍歷兩個(gè)MAC地址之間所有MAC的方法
這篇文章主要介紹了Java編程實(shí)現(xiàn)遍歷兩個(gè)MAC地址之間所有MAC的方法,涉及Java針對(duì)MAC的遍歷獲取與字符串轉(zhuǎn)換相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
SpringBoot簡(jiǎn)單實(shí)現(xiàn)定時(shí)器過程
這篇文章主要介紹了SpringBoot簡(jiǎn)單實(shí)現(xiàn)定時(shí)器過程,對(duì)于Java后端來說肯定實(shí)現(xiàn)定時(shí)功能肯定是使用到Spring封裝好的定時(shí)調(diào)度Scheduled2023-04-04
IDEA下因Lombok插件產(chǎn)生的Library source does not match the bytecode報(bào)
這篇文章主要介紹了IDEA下因Lombok插件產(chǎn)生的Library source does not match the bytecode報(bào)錯(cuò)問題及解決方法,親測(cè)試過好用,需要的朋友可以參考下2020-04-04
關(guān)于spring5的那些事:@Indexed 解密
這篇文章主要介紹了關(guān)于spring5的那些事:@Indexed 解密,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
SpringBoot?把PageHelper分頁信息返回給前端的方法步驟
本文主要介紹了SpringBoot?把PageHelper分頁信息返回給前端的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01
Java中StringBuilder與StringBuffer的區(qū)別
在Java編程中,字符串的拼接是一項(xiàng)常見的操作。為了有效地處理字符串的拼接需求,Java提供了兩個(gè)主要的類:StringBuilder和StringBuffer,本文主要介紹了Java中StringBuilder與StringBuffer的區(qū)別,感興趣的可以了解一下2023-08-08

