Kaynağa Gözat

feat: "完成Spring Security Token解析和@PreAuthorize使用"

Leonezhurui 2 yıl önce
ebeveyn
işleme
3caf707de7

+ 54 - 0
src/main/java/com/njuzr/eaibackend/config/JWTAuthenticationFilter.java

@@ -0,0 +1,54 @@
+package com.njuzr.eaibackend.config;
+
+import com.njuzr.eaibackend.po.MyUserDetails;
+import com.njuzr.eaibackend.service.MyUserDetailService;
+import com.njuzr.eaibackend.utils.JWTTokenUtil;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/20 - 21:51
+ * @Package: EAI-Backend
+ */
+
+@Slf4j
+public class JWTAuthenticationFilter extends OncePerRequestFilter {
+
+    private final JWTTokenUtil jwtTokenUtil;
+    private final MyUserDetailService userDetailService;
+
+    public JWTAuthenticationFilter(JWTTokenUtil jwtTokenUtil, MyUserDetailService userDetailService) {
+        this.jwtTokenUtil = jwtTokenUtil;
+        this.userDetailService = userDetailService;
+    }
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+        String token = request.getHeader("Authorization");
+        log.info("从Header中解析出Token:" + token);
+
+        if (token != null) {
+            String username = jwtTokenUtil.parseToken(token);
+            UserDetails userDetails = userDetailService.loadUserByUsername(username);
+            log.info("解析出UserDetails:" + userDetails);
+
+            UsernamePasswordAuthenticationToken authentication =
+                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
+        }
+
+        chain.doFilter(request, response);
+    }
+}

+ 44 - 0
src/main/java/com/njuzr/eaibackend/config/JWTAuthenticationToken.java

@@ -0,0 +1,44 @@
+package com.njuzr.eaibackend.config;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/20 - 21:50
+ * @Package: EAI-Backend
+ */
+
+public class JWTAuthenticationToken extends AbstractAuthenticationToken {
+
+    private final Object principal;
+    private String jwtToken;
+
+    public JWTAuthenticationToken(String jwtToken) {
+        super(null);
+        this.principal = null;
+        this.jwtToken = jwtToken;
+        setAuthenticated(false);
+    }
+
+    public JWTAuthenticationToken(UserDetails principal, String jwtToken) {
+        super(principal.getAuthorities());
+        this.principal = principal;
+        this.jwtToken = jwtToken;
+        super.setAuthenticated(true); // must use super, as we override
+    }
+
+    @Override
+    public Object getCredentials() {
+        return null;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return this.principal;
+    }
+
+    public String getJwtToken() {
+        return jwtToken;
+    }
+}

+ 19 - 7
src/main/java/com/njuzr/eaibackend/config/SecurityConfig.java

@@ -1,21 +1,20 @@
 package com.njuzr.eaibackend.config;
 
+import com.njuzr.eaibackend.exception.MyAccessDeniedHandler;
 import com.njuzr.eaibackend.exception.MyAuthenticationEntryPoint;
 import com.njuzr.eaibackend.service.MyUserDetailService;
+import com.njuzr.eaibackend.utils.JWTTokenUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
 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.configurers.AbstractHttpConfigurer;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.NoOpPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -28,16 +27,23 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
 
 @Configuration
 @EnableWebSecurity
+@EnableMethodSecurity(prePostEnabled = true)
 public class SecurityConfig {
 
     private final MyAuthenticationEntryPoint unauthorizedHandler;
 
+    private final MyAccessDeniedHandler accessDeniedHandler;
+
     private final MyUserDetailService userDetailsService;
 
+    private final JWTTokenUtil jwtTokenUtil;
+
     @Autowired
-    public SecurityConfig(MyAuthenticationEntryPoint unauthorizedHandler, MyUserDetailService userDetailsService) {
+    public SecurityConfig(MyAuthenticationEntryPoint unauthorizedHandler, MyAccessDeniedHandler accessDeniedHandler, MyUserDetailService userDetailsService, JWTTokenUtil jwtTokenUtil) {
         this.unauthorizedHandler = unauthorizedHandler;
+        this.accessDeniedHandler = accessDeniedHandler;
         this.userDetailsService = userDetailsService;
+        this.jwtTokenUtil = jwtTokenUtil;
     }
 
     @Bean
@@ -51,10 +57,11 @@ public class SecurityConfig {
                 )
                 .exceptionHandling(exception -> exception
                         .authenticationEntryPoint(unauthorizedHandler)) // 认证失败处理(未提供认证信息)
+                .exceptionHandling(exception -> exception.accessDeniedHandler(accessDeniedHandler)) // 权限不够
                 .csrf(AbstractHttpConfigurer::disable) // 禁用CSRF
                 .formLogin(AbstractHttpConfigurer::disable) // 禁用登陆表单
                 .httpBasic(AbstractHttpConfigurer::disable) // 禁用http basic
-        ;
+                .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // 设置为无状态
 
         // 获取AuthenticationManager
         AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
@@ -67,7 +74,11 @@ public class SecurityConfig {
         authenticationFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
         authenticationFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
 
+        // JWT过滤器(在过滤器中已经实现了Token解析,不通过Provider解析)
+        JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(jwtTokenUtil, userDetailsService);
+
         // 将自定义的Filter添加到Spring Security过滤器链中
+        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
         http.addFilter(authenticationFilter);
 
         return http.build();
@@ -83,4 +94,5 @@ public class SecurityConfig {
         return new BCryptPasswordEncoder();
     }
 
+
 }

+ 6 - 20
src/main/java/com/njuzr/eaibackend/controller/UserController.java

@@ -8,6 +8,7 @@ import com.njuzr.eaibackend.utils.ModelMapperUtil;
 import com.njuzr.eaibackend.vo.UserVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 /**
@@ -28,25 +29,10 @@ public class UserController {
         this.userService = userService;
     }
 
+    @GetMapping("/{id}")
+    @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_TEACHER')")
+    public MyResponse findUserById(@PathVariable Long id) {
+        return MyResponse.success(id);
+    }
 
-
-//    @PostMapping("/login")
-//    public MyResponse login(@RequestBody UserLoginVO userLoginVO) {
-//        try {
-//            Authentication authentication = authenticationManager.authenticate(
-//                    new UsernamePasswordAuthenticationToken(
-//                            userLoginVO.getOfficialEmail(),
-//                            userLoginVO.getPassword()
-//                    )
-//            );
-//
-//            SecurityContextHolder.getContext().setAuthentication(authentication);
-//
-//            String jwt = jwtTokenUtil.generateToken(authentication);
-//            return ResponseEntity.ok(new JwtResponse(jwt));
-//        } catch (AuthenticationException e) {
-//            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Error: Authentication failed");
-//        }
-//
-//    }
 }

+ 34 - 0
src/main/java/com/njuzr/eaibackend/exception/MyAccessDeniedHandler.java

@@ -0,0 +1,34 @@
+package com.njuzr.eaibackend.exception;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.njuzr.eaibackend.controller.MyResponse;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/21 - 07:43
+ * @Package: EAI-Backend
+ */
+
+@Slf4j
+@Component
+public class MyAccessDeniedHandler implements AccessDeniedHandler {
+    private final ObjectMapper objectMapper = new ObjectMapper();
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException, ServletException {
+        log.error("认证出错,用户权限不够");
+        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+        response.setContentType("application/json");
+        MyResponse myResponse = MyResponse.error(HttpServletResponse.SC_FORBIDDEN, "Authentication forbidden: " + exception.getMessage());
+        response.getWriter().write(objectMapper.writeValueAsString(myResponse));
+        response.getWriter().flush();
+    }
+}

+ 0 - 4
src/main/java/com/njuzr/eaibackend/po/base/BaseUser.java

@@ -37,8 +37,4 @@ public class BaseUser {
     private String phone; // 联系方式(选填)
 
     private String contentEmail; // 联系邮箱(选填)
-
-    boolean isAccountNonLocked = false; // 账户是否被锁定
-
-    boolean enabled = false; // 账户是否可用
 }

+ 0 - 6
src/main/java/com/njuzr/eaibackend/utils/JWTTokenUtil.java

@@ -2,18 +2,12 @@ package com.njuzr.eaibackend.utils;
 
 import io.jsonwebtoken.*;
 import io.jsonwebtoken.security.Keys;
-import io.jsonwebtoken.security.SecureDigestAlgorithm;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.stereotype.Component;
 
-import javax.crypto.SecretKey;
-import java.security.Key;
 import java.time.Instant;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.UUID;
 
 @Slf4j