點擊上方藍色字體,選擇「標星公眾號」
優質文章,第一時間送達
66套java從入門到精通實戰課程分享
1、前言部分1.1、嘮嗑部分(如何學習?)看springsecurtiy原理圖的時候以為灑灑水,媽的,結果自己動手做的時候一竅不通,所以一定不要眼高手低,實踐出真知!通過各種方式學習springsecurity,在B站、騰訊課堂、網易課堂、慕課網沒有springsecurity的前後端分離的教學視頻,那我就去csdn去尋找springsecurity博客,發現幾個問題:
實在不行我又跑去github上找開源項目學習,github由於是外國網站,國內訪問速度有點慢!!那就用國內的gitee吧,gitee上的開源項目都是結合實戰項目的,代碼邏輯也比較複雜,我對項目的業務邏輯沒什麼了解,感覺不適合我。我這一次選擇比較反人性的方式去學習,就是手撕源碼和看官方文檔。老實講,剛開始看源碼和官方文檔特別難受,並且看不進去,那些springsecurity的類還有接口名字又臭又長,這時我就下載源碼,源碼的注釋多的就像一本書,非常詳細且權威。
當然別指望看幾遍就能看懂,我看這些注釋、源碼、博客看了10幾遍甚至20幾遍才看懂,每次去看都有不同的收穫!!!
此文章截圖水平不高、理解為主、欣賞為輔!!內容有點多,每一步都有詳細解析,請耐心看完,看不懂可以多看幾遍。。
1.2、技術支持jdk 1.8、springboot 2.3.4、mybatis-plus 3.4.1、mysql 5.5、springsecurity 5.3.4、springmvc、lombok簡化entity代碼,不用你去寫get、set方法,全部自動生成、gson 2.8.2 將json對象轉化成json字符串
1.3、預期實現效果圖未登錄時訪問指定資源, 返回未登錄的json字符串 , index是我在controller層寫的一個簡單接口,返回index字符串
輸入帳號錯誤,返回用戶名錯誤的json字符串 , 需說明一點,/login是springsecurity封裝好的接口,無須你在controller寫login接口,/logout也同理。
輸入密碼錯誤,返回密碼錯誤的json字符串
登錄成功, 返回登錄成功的json字符串並返回cookie
登錄成功並且擁有權限訪問指定資源, 返回資源相關數據的json字符串
登錄成功但無權限訪問指定資源時,返回權限不足的json字符串
異地登錄,返回異地登錄,強制下線的json字符串 , 測試的基礎是要在兩臺不同的機器上登錄,然後訪問/index。
註銷成功,返回註銷成功的json字符串並刪除cookie
2、核心部分
2.1、springsecurity原理解釋:springsecurity最重要的兩個部分: authentication(認證) 和 authorization(授權)
認證: 就是判定你是什麼身份,管理員還是普通人
授權: 什麼樣的身份擁有什麼樣的權利。
簡單理解: 自定義配置登錄成功、登陸失敗、註銷成功目標結果類,並將其注入到springsecurity的配置文件中。如何認證、授權交給AuthenticationManager去作
複雜理解:
(1)用戶發起表單登錄請求後,首先進入 UsernamePasswordAuthenticationFilter, 在 UsernamePasswordAuthenticationFilter中根據用戶輸入的用戶名、密碼構建了 UsernamePasswordAuthenticationToken,並將其交給 AuthenticationManager 來進行認證處理。
AuthenticationManager 本身不包含認證邏輯,其核心是用來管理所有的 AuthenticationProvider,通過交由合適的 AuthenticationProvider 來實現認證。
(2)下面跳轉到了 SelfAuthenticationProvider,該類是 AuthenticationProvider 的實現類:你可以在該類的 Authentication authenticate(Authentication authentication) 自定義認證邏輯, 然後在該類中通過調用UserDetails loadUserByUsername(account) 去獲取資料庫用戶信息並驗證,然後創建 UsernamePasswordAuthenticationToken 並將權限、用戶個人信息注入到其中 ,並通過setAuthenticated(true) 設置為需要驗證。
(3) 至此認證信息就被傳遞迴 UsernamePasswordAuthenticationFilter 中,在 UsernamePasswordAuthenticationFilter 的父類 AbstractAuthenticationProcessingFilter 的 doFilter() 中,會根據認證的成功或者失敗調用相應的 handler:所謂的handler就是我們注入到springsecurity配置文件的handler。
2.2、踩坑集錦
訪問/login時必須要用post方法!, 訪問的參數名必須為username和password
訪問/logout時即可用post也可用get方法!
//springsecurity配置文件中的hasRole("")不能以ROLE開頭,比如ROLE_USER就是錯的,springsecurity會默認幫我們加上,但資料庫的權限欄位必須是ROLE_開頭,否則讀取不到
.antMatchers("/index").hasRole("USER")
.antMatchers("/hello").hasRole("ADMIN")
pom依賴文件
<dependencies>
<!--轉換成json字符串的工具-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<!--springboot集成web操作7-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springsecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus依賴-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--springboot-自帶測試工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.3.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.3.4.RELEASE</version>
</dependency>
</dependencies>Msg.java 自定義返回結果集,這個看個人的,怎麼開心怎麼來!
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Msg {
int code; //錯誤碼
String Message; //消息提示
Map<String,Object> data=new HashMap<String,Object>(); //數據
//無權訪問
public static Msg denyAccess(String message){
Msg result=new Msg();
result.setCode(300);
result.setMessage(message);
return result;
}
//操作成功
public static Msg success(String message){
Msg result=new Msg();
result.setCode(200);
result.setMessage(message);
return result;
}
//客戶端操作失敗
public static Msg fail(String message){
Msg result=new Msg();
result.setCode(400);
result.setMessage(message);
return result;
}
public Msg add(String key,Object value){
this.data.put(key,value);
return this;
}
}User.java ,此類是entity實體類
@Data
public class User implements Serializable {
private Integer id;
private String account;
private String password;
private String role;
}UserMapper.java ,此接口繼承 BaseMapper<T>類,而BaseMapper<T>類 封裝了大量的sql,極大程度簡化了程式設計師sql語句的書寫.
@Repository
public interface UserMapper extends BaseMapper<User> {
}正常情況下要寫UserService.java接口,但是此文章只是用於演示效果,就沒書寫了
public interface UserService{
}UserServiceImpl.java,使其實現 UserDetailsService接口, 從而去獲取資料庫用戶信息,詳細解析請看注釋部分。
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService,UserDetailsService {
@Autowired
UserMapper userMapper;
//加載用戶
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//mybatis-plus幫我們寫好了sql語句,相當於 select * from user where account ='${account}'
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.eq("account",s);
User user=userMapper.selectOne(wrapper); //user即為查詢結果
if(user==null){
throw new UsernameNotFoundException("用戶名錯誤!!");
}
//獲取用戶權限,並把其添加到GrantedAuthority中
List<GrantedAuthority> grantedAuthorities=new ArrayList<>();
GrantedAuthority grantedAuthority=new SimpleGrantedAuthority(user.getRole());
grantedAuthorities.add(grantedAuthority);
//方法的返回值要求返回UserDetails這個數據類型, UserDetails是接口,找它的實現類就好了
//new org.springframework.security.core.userdetails.User(String username,String password,Collection<? extends GrantedAuthority> authorities) 就是它的實現類
return new org.springframework.security.core.userdetails.User(s,user.getPassword(),grantedAuthorities);
}
}UserController.java 就是普通的controller
@RestController
public class UserController {
@GetMapping("index")
public String index(){
return "index";
}
@GetMapping("hello")
public String hello(){
return "hello";
}
}AuthenticationEnryPoint .java 自定義未登錄的處理邏輯
@Component
public class AuthenticationEnryPoint implements AuthenticationEntryPoint {
@Autowired
Gson gson;
//未登錄時返回給前端數據
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
Msg result=Msg.fail("需要登錄!!");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(gson.toJson(result));
}
}AuthenticationFailure.java 自定義登錄失敗時的處理邏輯
//登錄失敗返回給前端消息
@Component
public class AuthenticationFailure implements AuthenticationFailureHandler{
@Autowired
Gson gson;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
Msg msg=null;
if(e instanceof UsernameNotFoundException){
msg=Msg.fail(e.getMessage());
}else if(e instanceof BadCredentialsException){
msg=Msg.fail("密碼錯誤!!");
}else {
msg=Msg.fail(e.getMessage());
}
//處理編碼方式,防止中文亂碼的情況
response.setContentType("text/json;charset=utf-8");
//返回給前臺
response.getWriter().write(gson.toJson(msg));
}
}AuthenticationSuccess.java 自定義登錄成功時的處理邏輯
@Component
public class AuthenticationSuccess implements AuthenticationSuccessHandler{
@Autowired
Gson gson;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//登錄成功時返回給前端的數據
Msg result=Msg.success("登錄成功!!!!!");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(gson.toJson(result));
}
}AuthenticationLogout.java 自定義註銷時的處理邏輯
@Component
public class AuthenticationLogout implements LogoutSuccessHandler{
@Autowired
Gson gson;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Msg result=Msg.success("註銷成功");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(gson.toJson(result));
}
}AccessDeny.java 自定義無權限訪問時的邏輯處理
//無權訪問
@Component
public class AccessDeny implements AccessDeniedHandler{
@Autowired
Gson gson;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
Msg result= Msg.denyAccess("無權訪問,need Authorities!!");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(gson.toJson(result));
}
}SessionInformationExpiredStrategy.java 自定義異地登錄、帳號下線時的邏輯處理
@Component
public class SessionInformationExpiredStrategy implements org.springframework.security.web.session.SessionInformationExpiredStrategy{
@Autowired
Gson gson;
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
Msg result= Msg.fail("您的帳號在異地登錄,建議修改密碼");
HttpServletResponse response=event.getResponse();
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(gson.toJson(result));
}
}SelfAuthenticationProvider.java 自定義認證邏輯處理
@Component
public class SelfAuthenticationProvider implements AuthenticationProvider{
@Autowired
UserServiceImpl userServiceImpl;
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String account= authentication.getName(); //獲取用戶名
String password= (String) authentication.getCredentials(); //獲取密碼
UserDetails userDetails= userServiceImpl.loadUserByUsername(account);
boolean checkPassword= bCryptPasswordEncoder.matches(password,userDetails.getPassword());
if(!checkPassword){
throw new BadCredentialsException("密碼不正確,請重新登錄!");
}
return new UsernamePasswordAuthenticationToken(account,password,userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}SpringsecurityConfig.java是springsecurity的配置,詳細解析請看注釋!!
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //開啟權限註解,默認是關閉的
public class SpringsecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
AuthenticationEnryPoint authenticationEnryPoint; //未登錄
@Autowired
AuthenticationSuccess authenticationSuccess; //登錄成功
@Autowired
AuthenticationFailure authenticationFailure; //登錄失敗
@Autowired
AuthenticationLogout authenticationLogout; //註銷
@Autowired
AccessDeny accessDeny; //無權訪問
@Autowired
SessionInformationExpiredStrategy sessionInformationExpiredStrategy; //檢測異地登錄
@Autowired
SelfAuthenticationProvider selfAuthenticationProvider; //自定義認證邏輯處理
@Bean
public UserDetailsService userDetailsService() {
return new UserServiceImpl();
}
//加密方式
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
//認證
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(selfAuthenticationProvider);
}
//授權
@Override
protected void configure(HttpSecurity http) throws Exception {
//cors()解決跨域問題,csrf()會與restful風格衝突,默認springsecurity是開啟的,所以要disable()關閉一下
http.cors().and().csrf().disable();
// /index需要權限為ROLE_USER才能訪問 /hello需要權限為ROLE_ADMIN才能訪問
http.authorizeRequests()
.antMatchers("/index").hasRole("USER")
.antMatchers("/hello").hasRole("ADMIN")
.and()
.formLogin() //開啟登錄
.permitAll() //允許所有人訪問
.successHandler(authenticationSuccess) // 登錄成功邏輯處理
.failureHandler(authenticationFailure) // 登錄失敗邏輯處理
.and()
.logout() //開啟註銷
.permitAll() //允許所有人訪問
.logoutSuccessHandler(authenticationLogout) //註銷邏輯處理
.deleteCookies("JSESSIONID") //刪除cookie
.and().exceptionHandling()
.accessDeniedHandler(accessDeny) //權限不足的時候的邏輯處理
.authenticationEntryPoint(authenticationEnryPoint) //未登錄是的邏輯處理
.and()
.sessionManagement()
.maximumSessions(1) //最多只能一個用戶登錄一個帳號
.expiredSessionStrategy(sessionInformationExpiredStrategy) //異地登錄的邏輯處理
;
}
}application.yml配置文件
server:
port: 80
spring:
datasource:
url: jdbc:mysql://localhost:3306/springsecurity_test?characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver最終結果在上面就有,源碼地址: https://gitee.com/liu-wenxin/springsecurity_demo.git , 使用 git clone https://gitee.com/liu-wenxin/springsecurity_demo.git 下載到本地。
版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本聲明。
本文連結:
https://blog.csdn.net/weixin_42375707/article/details/110678638
粉絲福利:Java從入門到入土學習路線圖
👇👇👇
感謝點讚支持下哈