Boo*_*aka 7 spring spring-security spring-boot
我有一个 Spring Boot 微服务,用于验证 JWT(由不同服务颁发)进行身份验证。它运行良好,我可以像这样访问控制器中的 JWT 详细信息:
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
// MyController.java
@RestController
@RequestMapping("/")
public class MyController {
@GetMapping()
public String someControllerMethod(@AuthenticationPrincipal Jwt jwt) {
int userId = Integer.parseInt(jwt.getClaim("userid"));
...
}
}
Run Code Online (Sandbox Code Playgroud)
效果很好。我可以从 JWT 中提取我需要的内容,然后使用正确的用户 ID 等继续与我的数据库对话。
然而,我发现必须使用 Jwt 类型在每个控制器中获取这些值有点乏味。有没有办法可以注入不同的类型作为@AuthenticationPrincipal?
例如,我自己的类已经从 JWT 中提取了所需的内容,并公开了类似.getUserId()返回 int 的内容?这也让我可以集中解析声明或抛出异常(如果它们不符合预期)的逻辑。
更新
经过更多谷歌洞穴探险后,似乎我有两个选择
选项1:@ControllerAdvice和@ModelAttribute
正如这个答案中所解释的。我可以做类似的事情:
import com.whatever.CustomPrincipal; // a basic "data" class with some properties, getters, setters and constructor
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
@ControllerAdvice
public class SecurityControllerAdvice {
@ModelAttribute
public CustomPrincipal customPrincipal(Authentication auth) throws Exception {
CustomPrincipal customPrincipal;
if (auth != null && auth.getPrincipal() instanceof Jwt) {
Jwt jwt = (Jwt) auth.getPrincipal();
String sessionId = jwt.getClaimAsString("sessionid");
int userId = Integer.parseInt(jwt.getClaimAsString("userid"));
customPrincipal = new CustomPrincipal(userId, sessionId);
} else {
// log an error and throw an exception?
}
return customPrincipal;
}
}
Run Code Online (Sandbox Code Playgroud)
进而
import com.whatever.CustomPrincipal;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestController;
@RestController
@ControllerAdvice
public class HelloWorldController {
@GetMapping("/controlleradvice")
public String index(@ModelAttribute CustomPrincipal cp) {
log.info(cp.getUserId());
return "whatever";
}
}
Run Code Online (Sandbox Code Playgroud)
这看起来相当简洁、整洁。1 个带有 @ControllerAdvice 的新课程,鲍勃是你的叔叔!
选项2:使用 jwtAuthenticationConverter()
这个答案显示了另一种方法,使用“转换器”,它似乎将默认主体从 JWT 转换为包含 JWT (.getCredentials()) 以及自定义对象的自定义对象(扩展 AbstractAuthenticationToken)像 CustomPrincipal (或者 User 类之类的)。
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors().disable()
.csrf().disable()
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer().jwt(customizer -> customizer.jwtAuthenticationConverter((new MyPrincipalJwtConvertor())));
return http.build();
}
}
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.jwt.Jwt;
public class MyPrincipalJwtConvertor implements Converter<Jwt, MyAuthenticationToken> {
@Override
public MyAuthenticationToken convert(Jwt jwt) {
var principal = new MyPrincipal(Integer.parseInt(jwt.getClaimAsString("userid")), jwt.getClaimAsString("sessionid"));
return new MyAuthenticationToken(jwt, principal);
}
}
@RestController
public class HelloWorldController {
@GetMapping("/converter")
public String converter(@AuthenticationPrincipal MyPrincipal myPrincipal) {
log.info("/converter triggered");
log.info("" + myPrincipal.getUserId());
return "woo";
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class MyPrincipal {
private int userId;
private String sessionId;
}
Run Code Online (Sandbox Code Playgroud)
选项 1 看起来简单得多。
但选项 2 很好,因为我有运行过滤器来执行额外的验证(例如验证 JWT 中的会话 ID)。当该过滤器运行时,当它调用 SecurityContext.getContext().getAuthentication().getPrincipal() 时,它将获取 MyPrincipal 对象,而不必调用 Jwt.getClaimAsString() 并对其进行强制转换等。
我想我是在问,这两种方法有我没有考虑过的优点和缺点吗?他们中的一个人是否可能以一种不应该的方式破坏/虐待某些东西?
或者是很相似,我应该选择我喜欢的那个?
小智 0
只是想添加与“选项 1”类似的内容。如果您在生成 jwt 之前使用用户名/密码格式登录用户,则可以将身份验证主体声明为您的 UserEntity 并从拦截器分配值。
\n控制器方法将是这样的:
\n//With this method the preAuthorize becomes redundant but I still like including it.\n\n@PreAuthorize("isAuthenticated()")\n @PutMapping("/updatePassword")\n public ResponseEntity<?> updatePassword(@AuthenticationPrincipal UserEntity loggedUser,\n @RequestBody PasswordUpdateRequest passwordRequest) {\n return userEntityService.updatePassword(loggedUser, passwordRequest);\n }\nRun Code Online (Sandbox Code Playgroud)\n例如,在您从 jwt 中声明用户 ID 后,您实际上会从 UserDetailsService 中的方法加载用户,如下所示:
\nUserEntity user = (UserEntity) userDetailsService.loadUserById(userId);\nRun Code Online (Sandbox Code Playgroud)\n然后声明一个 UsernamePasswordAuthenticationToken 将用于在上下文中设置,如下所示:
\nUsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user,\n user.getRoles(), user.getAuthorities());\n authentication.setDetails(new WebAuthenticationDetails(request));\n\n SecurityContextHolder.getContext().setAuthentication(authentication);\nRun Code Online (Sandbox Code Playgroud)\n要实现此拦截器,请确保使用 addFilterBefore,以便我们可以在 UsernamePasswordAuthenticationFilter 之前实现此拦截器。
\n如果您使用SecurityFilterChain,请执行以下操作:
\n@Bean\n public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n http.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);\n return http.build();\n }\nRun Code Online (Sandbox Code Playgroud)\n如果使用旧的配置方法,那么它是相同的,但不要\xc2\xb4t 写“return http.build();” 部分。
\n在这两个中,您还可以使用 antmatchers 来限制哪种用户可以通过方法 .hasRole() 和 .hasAnyRole() 使用方法。
\n我还建议设置一个自定义的 AuthenticationEntryPoint 来进行异常处理,并从注释为 bean 的 CorsConfigurationSource 启用 cors(在安全过滤器链中声明 http.cors(),springboot 将自动选择此 cors 配置)。
\n我喜欢这样做的原因是,从生成令牌的微服务到仅验证它的其他微服务,代码仅发生很小的变化,这也使得使用我的其余端点的人们可以使用以下方式创建服务:如果他们需要登录用户的数据,则只需最少的努力,因为他们只需要在控制器中声明 @AuthenticationPrincipal UserEntity 用户。
\n这是我第一次回答,所以如果我以错误的方式回答或者只是没有正确解释自己,请告诉我。
\n| 归档时间: |
|
| 查看次数: |
9041 次 |
| 最近记录: |