英文:
How to implement Spring Boot Microservice with two levels of token authentication using Spring Security?
问题
你好团队,
我正在一个Spring Boot项目中工作,我想要使用Spring Security + JWT来设置两个级别的令牌身份验证。
我的一些同事已经使用Dropwizard框架构建了一个类似的应用程序。
我想在我的Spring Boot项目中实现相同的架构。我在问题末尾添加了API架构的链接。
我已经能够通过Spring Boot(使用Spring Security + JWT)设置第一个级别的令牌身份验证,但我找不到设置第二个级别的令牌身份验证的正确方法。
我尝试搜索相关文章,但找不到任何信息。
如果您能够分享一个代码片段,以在Spring Boot中实现两个级别的令牌身份验证(使用Spring Security),以便更好地理解,将会很有帮助。
提前感谢您的帮助!
英文:
Hello Team,
I am working on a Spring Boot project in which I want to setup two levels of token authentication using Spring Security + JWT.
A similar application has already been built by some of my colleagues using Dropwizard framework.
I want to implement the same architecture in my Spring Boot project. I have added link to the architecture of the API at the end of this question.
I am able to setup the first level of token authentication with Spring Boot (using Spring Security + JWT), but I am unable to find the correct way of setting up second level of token authentication.
I tried searching out for related articles but couldn't find any.
It would be helpful if you could share a code snippet implementing both levels of token authentication in Spring Boot (using Spring Security) for better understanding.
Thanking you in anticipation!
答案1
得分: -1
以下是一个概念验证,以实现您需要的内容。第一个类是安全配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final List<String> WHITE_LIST = asList("/authenticate");
public static final List<String> TOKEN1_LIST = asList("/get-access-token");
public static final List<String> TOKEN2_LIST = asList("/add-new-user");
@Autowired
private Token1Filter token1Filter;
@Autowired
private Token2Filter token2Filter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests()
.antMatchers(OPTIONS).permitAll()
.antMatchers(GET, WHITE_LIST.toArray(new String[WHITE_LIST.size()])).permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(token1Filter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(token2Filter, UsernamePasswordAuthenticationFilter.class);
}
}
不同的URL包含在不同的属性中,与它们相关的每个List
都在HttpSecurity
类中进行了配置。用于管理每个子集的过滤器,即使用Jwt 1和Jwt 2保护的URL如下:
@AllArgsConstructor
@Component
public class Token1Filter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
getJwt(request)
.ifPresent(jwt1 -> {
/**
* 在这里,您可以使用功能来检查提供的Jwt令牌1,
* 将包含的数据添加到Spring SecurityContextHolder中。
*/
// 用于测试目的
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("testUserToken1", null, new ArrayList<>())
);
});
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return !WebSecurityConfiguration.TOKEN1_LIST.contains(request.getRequestURI());
}
private Optional<String> getJwt(HttpServletRequest request) {
return ofNullable(request)
.map(r -> r.getHeader(AUTHORIZATION))
.filter(Predicate.not(String::isEmpty))
.map(t -> t.replace(TOKEN_PREFIX, ""))
.filter(Predicate.not(String::isEmpty));
}
}
@AllArgsConstructor
@Component
public class Token2Filter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
getJwt(request)
.ifPresent(jwt2 -> {
/**
* 在这里,您可以使用功能来检查提供的Jwt令牌2,
* 将包含的数据添加到Spring SecurityContextHolder中。
*/
// 用于测试目的
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("testUserToken2", null,
asList(new SimpleGrantedAuthority("ADMIN")))
);
});
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return !WebSecurityConfiguration.TOKEN2_LIST.contains(request.getRequestURI());
}
private Optional<String> getJwt(HttpServletRequest request) {
return ofNullable(request)
.map(r -> r.getHeader(AUTHORIZATION))
.filter(Predicate.not(String::isEmpty))
.map(t -> t.replace(TOKEN_PREFIX, ""))
.filter(Predicate.not(String::isEmpty));
}
}
每个过滤器都包含shouldNotFilter
方法的实现,以确定是否需要处理当前请求。并且包含了在提取和验证提供的Jwt令牌时需要添加功能的位置的注释。
最后,只有一个虚拟控制器来测试每个用例:
@AllArgsConstructor
@RestController
public class TestController {
@GetMapping("/authenticate")
public ResponseEntity<String> authenticate(@RequestParam String username, @RequestParam String password) {
return new ResponseEntity("[authenticate] Testing purpose", OK);
}
@GetMapping("/get-access-token")
public ResponseEntity<String> getAccessToken() {
return new ResponseEntity("[get-access-token] Testing purpose", OK);
}
@GetMapping("/add-new-user")
@PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<String> addNewUser() {
return new ResponseEntity("[add-new-user] Testing purpose", OK);
}
}
可以添加一些改进,但这是一个很好的起始点,可以正常工作。
英文:
The following is a proof of concept to achieve what you need. The first class is the security configuration:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final List<String> WHITE_LIST = asList("/authenticate");
public static final List<String> TOKEN1_LIST = asList("/get-access-token");
public static final List<String> TOKEN2_LIST = asList("/add-new-user");
@Autowired
private Token1Filter token1Filter;
@Autowired
private Token2Filter token2Filter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
// Make sure we use stateless session; session won't be used to store user's state
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// Handle an authorized attempts
.exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests()
// List of services do not require authentication
.antMatchers(OPTIONS).permitAll()
.antMatchers(GET, WHITE_LIST.toArray(new String[WHITE_LIST.size()])).permitAll()
// Any other request must be authenticated
.anyRequest().authenticated()
.and()
.addFilterBefore(token1Filter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(token2Filter, UsernamePasswordAuthenticationFilter.class);
}
}
As you can see, the different Urls are included in different properties and every List
related with them is configured in HttpSecurity
class. The filters used to manage every subset, I mean, Urls securized using Jwt 1 and Jwt 2 are the following ones:
@AllArgsConstructor
@Component
public class Token1Filter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
getJwt(request)
.ifPresent(jwt1 -> {
/**
* Here you can use functionality to check provided Jwt token 1,
* adding included data into Spring SecurityContextHolder.
*/
// Used for testing purpose
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("testUserToken1", null, new ArrayList<>())
);
});
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return !WebSecurityConfiguration.TOKEN1_LIST.contains(request.getRequestURI());
}
private Optional<String> getJwt(HttpServletRequest request) {
return ofNullable(request)
.map(r -> r.getHeader(AUTHORIZATION))
.filter(Predicate.not(String::isEmpty))
.map(t -> t.replace(TOKEN_PREFIX, ""))
.filter(Predicate.not(String::isEmpty));
}
}
@AllArgsConstructor
@Component
public class Token2Filter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
getJwt(request)
.ifPresent(jwt2 -> {
/**
* Here you can use functionality to check provided Jwt token 2,
* adding included data into Spring SecurityContextHolder.
*/
// Used for testing purpose
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("testUserToken2", null,
asList(new SimpleGrantedAuthority("ADMIN")))
);
});
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return !WebSecurityConfiguration.TOKEN2_LIST.contains(request.getRequestURI());
}
private Optional<String> getJwt(HttpServletRequest request) {
return ofNullable(request)
.map(r -> r.getHeader(AUTHORIZATION))
.filter(Predicate.not(String::isEmpty))
.map(t -> t.replace(TOKEN_PREFIX, ""))
.filter(Predicate.not(String::isEmpty));
}
}
Every one include an implementation of shouldNotFilter
method to know if it have to manage the current request. And contains a comment on location on which you need to add the functionality to extract and verify the provided Jwt token.
And finally, only a dummy controller to test every use case:
@AllArgsConstructor
@RestController
public class TestController {
@GetMapping("/authenticate")
public ResponseEntity<String> authenticate(@RequestParam String username, @RequestParam String password) {
return new ResponseEntity("[authenticate] Testing purpose", OK);
}
@GetMapping("/get-access-token")
public ResponseEntity<String> getAccessToken() {
return new ResponseEntity("[get-access-token] Testing purpose", OK);
}
@GetMapping("/add-new-user")
@PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<String> addNewUser() {
return new ResponseEntity("[add-new-user] Testing purpose", OK);
}
}
Several improvements could be added but this is a good initial point that works as expected.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论