Springboot整合SpringSecurity的完整案例詳解
一.Spring Security介紹
Spring Security是基于Spring生態(tài)圈的,用于提供安全訪問控制解決方案的框架。Spring Security的安 全管理有兩個重要概念,分別是Authentication(認證)和Authorization(授權)。 為了方便Spring Boot項目的安全管理,Spring Boot對Spring Security安全框架進行了整合支持,并提 供了通用的自動化配置,從而實現(xiàn)了Spring Security安全框架中包含的多數(shù)安全管理功能。
Spring Security登錄認證主要涉及兩個重要的接口 UserDetailService和UserDetails接口。
UserDetailService接口主要定義了一個方法 loadUserByUsername(String username)用于完成用戶信息的查 詢,其中username就是登錄時的登錄名稱,登錄認證時,需要自定義一個實現(xiàn)類實現(xiàn)UserDetailService接 口,完成數(shù)據(jù)庫查詢,該接口返回UserDetail。
UserDetail主要用于封裝認證成功時的用戶信息,即UserDetailService返回的用戶信息,可以用Spring
自己的User對象,但是最好是實現(xiàn)UserDetail接口,自定義用戶對象。
二.Spring Security認證步驟
1. 自定UserDetails類:當實體對象字段不滿足時需要自定義UserDetails,一般都要自定義
UserDetails。
2. 自定義UserDetailsService類,主要用于從數(shù)據(jù)庫查詢用戶信息。
3. 創(chuàng)建登錄認證成功處理器,認證成功后需要返回JSON數(shù)據(jù),菜單權限等。
4. 創(chuàng)建登錄認證失敗處理器,認證失敗需要返回JSON數(shù)據(jù),給前端判斷。
5. 創(chuàng)建匿名用戶訪問無權限資源時處理器,匿名用戶訪問時,需要提示JSON。
6. 創(chuàng)建認證過的用戶訪問無權限資源時的處理器,無權限訪問時,需要提示JSON。
7. 配置Spring Security配置類,把上面自定義的處理器交給Spring Security。
三.Spring Security認證實現(xiàn)
3.1添加Spring Security依賴
在pom.xml文件中添加Spring Security核心依賴,代碼如下所
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
3.2自定義UserDetails
當實體對象字段不滿足時Spring Security認證時,需要自定義UserDetails。
1. 將User類實現(xiàn)UserDetails接口
2. 將原有的isAccountNonExpired、isAccountNonLocked、isCredentialsNonExpired和isEnabled屬性修 改成boolean類型,同時添加authorities屬性。
@Data @TableName("sys_user") public class User implements Serializable, UserDetails { //省略原有的屬性...... /** * 帳戶是否過期(1 未過期,0已過期) */ private boolean isAccountNonExpired = true; /** * 帳戶是否被鎖定(1 未過期,0已過期) */ private boolean isAccountNonLocked = true; /** * 密碼是否過期(1 未過期,0已過期) */ private boolean isCredentialsNonExpired = true; /** * 帳戶是否可用(1 可用,0 刪除用戶) */ private boolean isEnabled = true; /** * 權限列表 */ @TableField(exist = false) Collection<? extends GrantedAuthority> authorities;
3.3.編寫Service接口
public interface UserService extends IService<User> { /** * 根據(jù)用戶名查詢用戶信息 * @param userName * @return */ User findUserByUserName(String userName); }
3.4.編寫ServiceImpl
package com.manong.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.manong.entity.User; import com.manong.dao.UserMapper; import com.manong.service.UserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * <p> * 服務實現(xiàn)類 * </p> * * @author lemon * @since 2022-12-06 */ @Service @Transactional public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Override public User findUserByUserName(String username) { //創(chuàng)建條件構造器對象 QueryWrapper queryWrapper=new QueryWrapper(); queryWrapper.eq("username",username); //執(zhí)行查詢 return baseMapper.selectOne(queryWrapper); } }
3.5. 自定義UserDetailsService類
package com.manong.config.security.service; import com.manong.entity.Permission; import com.manong.entity.User; import com.manong.service.PermissionService; import com.manong.service.UserService; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; /* * 用戶認證處理器類 * */ @Component public class CustomerUserDetailService implements UserDetailsService { @Resource private UserService userService; @Resource private PermissionService permissionService; public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{ //根據(jù)對象查找用戶信息 User user = userService.findUserByUserName(username); //判斷對象是否為空 if(user==null){ throw new UsernameNotFoundException("用戶的賬號密碼錯誤"); } //查詢當前登錄用戶擁有權限列表 List<Permission> permissionList = permissionService.findPermissionListByUserId(user.getId()); //獲取對應的權限編碼 List<String> codeList = permissionList.stream() .filter(Objects::nonNull) .map(item -> item.getCode()) .filter(Objects::nonNull) .collect(Collectors.toList()); //將權限編碼轉(zhuǎn)換成數(shù)據(jù) String [] strings=codeList.toArray(new String[codeList.size()]); //設置權限列表 List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(strings); //將權限列表設置給User user.setAuthorities(authorityList); //設置該用戶擁有的菜單 user.setPermissionList(permissionList); //查詢成功 return user; } }
四.通常情況下,我們需要自定義四個類來獲取處理類 包括成功,失敗,匿名用戶,登錄了但沒有權限的用戶
4.1.成功
package com.manong.config.security.handler; import com.alibaba.fastjson.serializer.SerializerFeature; import com.manong.entity.User; import com.manong.utils.JwtUtils; import com.manong.utils.LoginResult; import com.manong.utils.ResultCode; import io.jsonwebtoken.Jwts; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import sun.net.www.protocol.http.AuthenticationHeader; import javax.annotation.Resource; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import com.alibaba.fastjson.JSON; /* * 登錄認證成功處理器類 * */ @Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Resource private JwtUtils jwtUtils; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //設置相應編碼格式 response.setContentType("application/json;charset-utf-8"); //獲取當前登錄用戶的信息 User user = (User) authentication.getPrincipal(); //創(chuàng)建token對象 String token = jwtUtils.generateToken(user); //設置token的秘鑰和過期時間 long expireTime = Jwts.parser() .setSigningKey(jwtUtils.getSecret()) .parseClaimsJws(token.replace("jwt_", "")) .getBody().getExpiration().getTime();//設置過期時間 //創(chuàng)建LOgin登錄對象 LoginResult loginResult=new LoginResult(user.getId(), ResultCode.SUCCESS,token,expireTime); //將對象轉(zhuǎn)換成json格式 //消除循環(huán)引用 String result = JSON.toJSONString(loginResult, SerializerFeature.DisableCircularReferenceDetect); //獲取輸出流 ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(result.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close(); } }
4.2 失敗
package com.manong.config.security.handler; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.baomidou.mybatisplus.extension.api.R; import com.manong.entity.User; import com.manong.utils.Result; import com.manong.utils.ResultCode; import org.springframework.security.authentication.*; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; @Component public class LoginFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { //設置相應編碼格式 response.setContentType("application/json;charset-utf-8"); //獲取輸出流 ServletOutputStream outputStream = response.getOutputStream(); //定義變量,保存異常信息 String message=null; //判斷異常類型 if(exception instanceof AccountExpiredException){ message="賬戶過期失敗"; } else if(exception instanceof BadCredentialsException){ message="用戶名的賬號密碼錯誤,登錄失敗"; } else if(exception instanceof CredentialsExpiredException){ message="密碼過期,登錄失敗"; } else if(exception instanceof DisabledException){ message="賬號過期,登錄失敗"; } else if(exception instanceof LockedException){ message="賬號被上鎖,登錄失敗"; } else if(exception instanceof InternalAuthenticationServiceException){ message="用戶不存在"; } else { message="登錄失敗"; } //將結果轉(zhuǎn)換為Json格式 String result = JSON.toJSONString(Result.error().code(ResultCode.ERROR).message(message)); //將結果保存到輸出中 outputStream.write(result.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close(); } }
4.3 匿名無用戶
package com.manong.config.security.handler; import com.alibaba.fastjson.serializer.SerializerFeature; import com.manong.entity.User; import com.manong.utils.Result; import com.manong.utils.ResultCode; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import sun.net.www.protocol.http.AuthenticationHeader; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import com.alibaba.fastjson.JSON; /* * 匿名訪問無權限資源處理器 * */ @Component public class AnonymousAuthenticationHandler implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setContentType("application/json;charset-utf-8"); //獲取輸出流 ServletOutputStream outputStream = response.getOutputStream(); //將對象轉(zhuǎn)換成json格式 //消除循環(huán)引用 String result = JSON.toJSONString(Result.error().code(ResultCode.NO_AUTH).message("匿名用戶無權限訪問"), SerializerFeature.DisableCircularReferenceDetect); //獲取輸出流 outputStream.write(result.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close(); } }
4.4 登錄了但是沒有權限的用戶
package com.manong.config.security.handler; import com.alibaba.fastjson.serializer.SerializerFeature; import com.manong.entity.User; import com.manong.utils.Result; import com.manong.utils.ResultCode; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import sun.net.www.protocol.http.AuthenticationHeader; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import com.alibaba.fastjson.JSON; /* * 認證用戶訪問無權限資源處理器 * */ @Component public class CustomerAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setContentType("application/json;charset-utf-8"); ServletOutputStream outputStream = response.getOutputStream(); //將對象轉(zhuǎn)換成json格式 //消除循環(huán)引用 String result = JSON.toJSONString(Result.error().code(ResultCode.NO_AUTH).message("用戶無權限訪問,請聯(lián)系教務處"), SerializerFeature.DisableCircularReferenceDetect); //獲取輸出流 outputStream.write(result.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close(); } }
五.編寫SpringSecurity配置類,把上面的四個類進行合并
package com.manong.config.security.service; import com.manong.config.security.handler.AnonymousAuthenticationHandler; import com.manong.config.security.handler.CustomerAccessDeniedHandler; import com.manong.config.security.handler.LoginFailureHandler; import com.manong.config.security.handler.LoginSuccessHandler; import org.springframework.context.annotation.Bean; 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.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Resource private LoginSuccessHandler loginSuccessHandler; @Resource private LoginFailureHandler loginFailureHandler; @Resource private CustomerAccessDeniedHandler customerAccessDeniedHandler; @Resource private AnonymousAuthenticationHandler anonymousAuthenticationHandler; @Resource private CustomerUserDetailService customerUserDetailService; //注入加密類 @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } //處理登錄認證 @Override protected void configure(HttpSecurity http) throws Exception { //登錄過程處理 http.formLogin() //表單登錄 .loginProcessingUrl("/api/user/login") //登錄請求url地址 .successHandler(loginSuccessHandler) //認證成功 .failureHandler(loginFailureHandler) //認證失敗 .and() .csrf().disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //不創(chuàng)建Session .and().authorizeRequests() //設置需要攔截的請求 .antMatchers("/api/user/login").permitAll()//登錄放行 .anyRequest().authenticated() //其他請求一律攔截 .and() .exceptionHandling() .authenticationEntryPoint(anonymousAuthenticationHandler) //匿名無權限類 .accessDeniedHandler(customerAccessDeniedHandler) //認證用戶無權限 .and() .cors();//支持跨域 } //認證配置處理器 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customerUserDetailService) .passwordEncoder(this.passwordEncoder());//密碼加密 } }
到此這篇關于Springboot整合SpringSecurity的文章就介紹到這了,更多相關Springboot整合SpringSecurity內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- SpringBoot啟動security后如何關閉彈出的/login頁面
- SpringBoot整合Spring Security構建安全的Web應用
- SpringBoot整合新版SpringSecurity完整過程
- SpringBoot集成Swagger使用SpringSecurity控制訪問權限問題
- SpringBoot集成SpringSecurity安全框架方式
- SpringSecurity在SpringBoot中的自動裝配過程
- Springbootadmin與security沖突問題及解決
- SpringBoot整合Springsecurity實現(xiàn)數(shù)據(jù)庫登錄及權限控制功能
- SpringBoot配置Spring?Security的實現(xiàn)示例
相關文章
Java創(chuàng)建可執(zhí)行JAR文件的多種方式
本文主要介紹了Java創(chuàng)建可執(zhí)行JAR文件的多種方式,使用JDK的jar工具、IDE、Maven和Gradle來創(chuàng)建和配置可執(zhí)行JAR文件,具有一定的參考價值,感興趣的可以了解一下2024-07-07springboot使用war包部署到外部tomcat過程解析
這篇文章主要介紹了springboot使用war包部署到外部tomcat過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01詳解SpringBoot的三種緩存技術(Spring Cache、Layering Cache 框架、Alibaba J
這篇文章主要介紹了SpringBoot的三種緩存技術,幫助大家更好的理解和學習springboot框架,感興趣的朋友可以了解下2020-10-10