Kaynağa Gözat

feat: "初步配置Spring Security"

Leonezhurui 2 yıl önce
ebeveyn
işleme
2c010acd6f

+ 17 - 0
pom.xml

@@ -72,6 +72,23 @@
         </dependency>
 
 
+        <!--spring security、jwt-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-jwt</artifactId>
+            <version>1.1.1.RELEASE</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>0.12.5</version>
+        </dependency>
+
+
     </dependencies>
 
     <build>

+ 30 - 0
src/main/java/com/njuzr/eaibackend/config/MyAuthenticationFailureHandler.java

@@ -0,0 +1,30 @@
+package com.njuzr.eaibackend.config;
+
+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 org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+import java.io.IOException;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/19 - 23:24
+ * @Package: EAI-Backend
+ */
+
+public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        response.setContentType("application/json");
+        MyResponse myResponse = MyResponse.error(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed: " + exception.getMessage());
+        response.getWriter().write(objectMapper.writeValueAsString(myResponse));
+        response.getWriter().flush();
+    }
+}

+ 32 - 0
src/main/java/com/njuzr/eaibackend/config/MyAuthenticationSuccessHandler.java

@@ -0,0 +1,32 @@
+package com.njuzr.eaibackend.config;
+
+import com.njuzr.eaibackend.controller.MyResponse;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/19 - 23:20
+ * @Package: EAI-Backend
+ */
+
+public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+        response.setStatus(HttpServletResponse.SC_OK); // 状态码200
+        response.setContentType("application/json");
+        MyResponse myResponse = MyResponse.success(authentication.getPrincipal());
+        response.getWriter().write(objectMapper.writeValueAsString(myResponse));
+        response.getWriter().flush();
+    }
+}

+ 85 - 0
src/main/java/com/njuzr/eaibackend/config/SecurityConfig.java

@@ -0,0 +1,85 @@
+package com.njuzr.eaibackend.config;
+
+import com.njuzr.eaibackend.exception.MyAuthenticationEntryPoint;
+import com.njuzr.eaibackend.service.MyUserDetailService;
+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.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.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;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/19 - 16:28
+ * @Package: EAI-Backend
+ */
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+    private final MyAuthenticationEntryPoint unauthorizedHandler;
+
+    private final MyUserDetailService userDetailsService;
+
+    @Autowired
+    public SecurityConfig(MyAuthenticationEntryPoint unauthorizedHandler, MyUserDetailService userDetailsService) {
+        this.unauthorizedHandler = unauthorizedHandler;
+        this.userDetailsService = userDetailsService;
+    }
+
+    @Bean
+    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+        http
+                .authorizeHttpRequests(authorize -> authorize
+//                                .anyRequest().permitAll() // 允许所有的请求访问
+                        .requestMatchers("/api/public/**").permitAll() // 允许公开访问的路径
+                        .anyRequest().authenticated() // 其他所有请求需要认证
+                )
+                .exceptionHandling(exception -> exception
+                        .authenticationEntryPoint(unauthorizedHandler)) // 认证失败处理(未提供认证信息)
+                .csrf(AbstractHttpConfigurer::disable) // 禁用CSRF
+                .formLogin(AbstractHttpConfigurer::disable) // 禁用登陆表单
+                .httpBasic(AbstractHttpConfigurer::disable) // 禁用http basic
+        ;
+
+        // 获取AuthenticationManager
+        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
+
+        // 配置UsernamePasswordAuthenticationFilter
+        UsernamePasswordAuthenticationFilter authenticationFilter = new UsernamePasswordAuthenticationFilter();
+        authenticationFilter.setAuthenticationManager(authenticationManager);
+
+        // 配置自定义认证成功和失败处理器
+        authenticationFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
+        authenticationFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
+
+        // 将自定义的Filter添加到Spring Security过滤器链中
+        http.addFilter(authenticationFilter);
+
+        return http.build();
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+        return authenticationConfiguration.getAuthenticationManager();
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+}

+ 6 - 1
src/main/java/com/njuzr/eaibackend/config/WebConfig.java

@@ -11,11 +11,16 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 @Configuration
 public class WebConfig implements WebMvcConfigurer {
+    //设置允许访问的来源
+    String[] origins = new String[]{
+            "http://localhost:3000",
+            "http://localhost:3001"
+    };
 
     @Override
     public void addCorsMappings(CorsRegistry registry) {
         registry.addMapping("/**") // 对所有路径应用规则
-                .allowedOrigins("http://localhost:3000")
+                .allowedOrigins(origins)
                 .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法
                 .allowedHeaders("*") // 允许的请求头
                 .allowCredentials(true) // 允许发送Cookies

+ 54 - 0
src/main/java/com/njuzr/eaibackend/controller/AuthenticationController.java

@@ -0,0 +1,54 @@
+//package com.njuzr.eaibackend.controller;
+//
+//import com.njuzr.eaibackend.vo.UserLoginVO;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.http.HttpStatus;
+//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.context.SecurityContextHolder;
+//import org.springframework.web.bind.annotation.PostMapping;
+//import org.springframework.web.bind.annotation.RequestBody;
+//import org.springframework.web.bind.annotation.RequestMapping;
+//import org.springframework.web.bind.annotation.RestController;
+//
+///**
+// * @author: Leonezhurui
+// * @Date: 2024/2/20 - 00:10
+// * @Package: EAI-Backend
+// */
+//
+//@RestController
+//@RequestMapping("/api/login")
+//public class AuthenticationController {
+//
+//    private final AuthenticationManager authenticationManager;
+//
+//    @Autowired
+//    public AuthenticationController(AuthenticationManager authenticationManager) {
+//        this.authenticationManager = authenticationManager;
+//    }
+//
+//    @PostMapping("/login")
+//    public MyResponse login(@RequestBody UserLoginVO userLoginVO) {
+//        try {
+//            Authentication authentication = authenticationManager.authenticate(
+//                    new UsernamePasswordAuthenticationToken(
+//                            userLoginVO.getOfficialEmail(),
+//                            userLoginVO.getPassword()
+//                    )
+//            );
+//
+//            SecurityContextHolder.getContext().setAuthentication(authentication);
+//
+//            // TODO 生成JWT
+//            String jwt = jwtTokenUtil.generateToken(authentication);
+//            return MyResponse.success({});
+//        } catch (AuthenticationException e) {
+//            return MyResponse.error(HttpStatus.UNAUTHORIZED.value(), "Error: Authentication failed");
+//        }
+//
+//    }
+//
+//}

+ 1 - 1
src/main/java/com/njuzr/eaibackend/controller/TestController.java

@@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.RestController;
  * @Package: EAI-Backend
  */
 @RestController
-@RequestMapping("/test")
+@RequestMapping("/api/test")
 public class TestController {
     @GetMapping
     public MyResponse test() {

+ 26 - 2
src/main/java/com/njuzr/eaibackend/controller/UserController.java

@@ -5,10 +5,14 @@ import com.njuzr.eaibackend.dto.UserRegisterDTO;
 import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.service.UserService;
 import com.njuzr.eaibackend.utils.ModelMapperUtil;
+import com.njuzr.eaibackend.vo.UserLoginVO;
 import com.njuzr.eaibackend.vo.UserRegisterVO;
 import com.njuzr.eaibackend.vo.UserVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.*;
 
 /**
@@ -19,7 +23,7 @@ import org.springframework.web.bind.annotation.*;
 
 @Slf4j
 @RestController
-@RequestMapping("/user")
+@RequestMapping("/api/user")
 public class UserController {
 
     private final UserService userService;
@@ -29,7 +33,7 @@ public class UserController {
         this.userService = userService;
     }
 
-    @PostMapping
+    @PostMapping("/register")
     public MyResponse createUser(@RequestBody UserRegisterVO userRegisterVO) {
         log.info("createUser - 参数解析:"+userRegisterVO.toString());
 
@@ -48,4 +52,24 @@ public class UserController {
 
         return MyResponse.success(userVO);
     }
+
+    @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");
+        }
+
+    }
 }

+ 37 - 0
src/main/java/com/njuzr/eaibackend/exception/MyAuthenticationEntryPoint.java

@@ -0,0 +1,37 @@
+package com.njuzr.eaibackend.exception;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.njuzr.eaibackend.controller.MyResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/19 - 16:56
+ * @Package: EAI-Backend
+ */
+
+
+@Slf4j
+@Component
+// 用于处理那些需要认证的请求但是用户未提供任何认证信息的情况
+public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response,
+                         AuthenticationException authException) throws IOException {
+        log.error("认证出错,用户未提供认证信息");
+        response.setContentType("application/json;charset=UTF-8");
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        // 以MyResponse的格式返回
+        MyResponse myResponse = MyResponse.error(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: " + authException.getMessage());
+        response.getWriter().write(mapper.writeValueAsString(myResponse));
+    }
+}

+ 25 - 0
src/main/java/com/njuzr/eaibackend/service/MyUserDetailService.java

@@ -0,0 +1,25 @@
+package com.njuzr.eaibackend.service;
+
+import com.njuzr.eaibackend.mapper.UserMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+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.Service;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/20 - 00:02
+ * @Package: EAI-Backend
+ */
+
+@Service
+public class MyUserDetailService implements UserDetailsService {
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        // TODO 调用UserService的findById方法,找到目标用户
+
+        return null;
+    }
+}

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

@@ -0,0 +1,10 @@
+package com.njuzr.eaibackend.utils;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/20 - 00:09
+ * @Package: EAI-Backend
+ */
+
+public class JWTTokenUtil {
+}

+ 15 - 0
src/main/java/com/njuzr/eaibackend/vo/UserLoginVO.java

@@ -0,0 +1,15 @@
+package com.njuzr.eaibackend.vo;
+
+import lombok.Data;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/20 - 00:06
+ * @Package: EAI-Backend
+ */
+
+@Data
+public class UserLoginVO {
+    private String officialEmail; // 南大官邮,xxx@smail.nju.edu.cn(必填)
+    private String password;
+}