获取 401 Unauthorized 即使用户已通过身份验证(Spring 安全)
Getting 401 Unauthorized Even when the user is authenticated (Spring Security)
我正在做一个简单的项目,它有 2 个定义的角色 Admin 和 User,admin 角色有权通过他们的用户名查看用户,但是当我以管理员身份登录并点击 /admin/users/usernames/{username}
我得到 401 Unauthenticated 你可以在这里看到请求 postman request
以下实现有什么问题?
**登录方式**
@Override
public ResponseEntity<User> login(String username, String password) {
authenticate(username,password);
User loginUser = findUserByUsername(username);
UserPrinciple userPrinciple = new UserPrinciple(loginUser);
HttpHeaders tokenHeader = getJwtToken(userPrinciple);
return new ResponseEntity<>(loginUser,tokenHeader, HttpStatus.OK);
}
private void authenticate(String username, String password) {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,password));
}
private HttpHeaders getJwtToken(UserPrinciple userPrinciple) {
String token= jwtTokenProvider.generateJWTToken(userPrinciple);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(SecurityConsts.JWT_TOKEN_HEADER,token);
return httpHeaders;
}
用户 ReST 控件
@RestController
@RequestMapping("/admin/users")
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/")
public List<User> getUsers() {
return userService.getUsers();
}
@GetMapping("/emails/{email}")
public User findUserByEmail(@PathVariable String email) {
return userService.findUserByEmail(email);
}
@GetMapping("/usernames/{username}")
public User findUserByUsername(@PathVariable String username) {
return userService.findUserByUsername(username);
}
@JsonView(BodyView.BasicUser.class)
@PostMapping("/register")
public User addUser(@RequestBody User user) throws UsernameExistsException {
return userService.addUser(user);
}
@JsonView(BodyView.BasicUser.class)
@PostMapping("/login")
public ResponseEntity<User> login(@RequestBody User user) {
return userService.login(user.getUsername(), user.getPassword());
}
}
POM.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>FM6-App</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>FM6-App</name>
<description>FM6-App</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
网络安全配置器适配器
public 网址:
public static final String[] PUBLIC_URLS ={"/users/admin/login/**","/admin/users/register/**","/demandes/**","/criteres/**"};
@Configuration
@EnableWebSecurity
@ComponentScan
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JWTAuthorizationFilter jwtAuthorizationFilter;
@Autowired
private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JWTAccessDenielHandler jwtAccessDenielHandler;
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/admin/users/login/**").permitAll()
.antMatchers(SecurityConsts.PUBLIC_URLS).permitAll()
.antMatchers("/admin/**").hasAnyAuthority("ROLE_ADMIN")
.antMatchers("/utilisateur/**").hasAnyAuthority("ROLE_USER")
// .anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(jwtAccessDenielHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
}
@Bean(name = "authenticationManager")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
JWT 授权过滤器
@Component
public class JWTAuthorizationFilter extends OncePerRequestFilter {
@Autowired
private JWTTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getMethod().equalsIgnoreCase(SecurityConsts.OPTION_HTTP_METHOD)){
response.setStatus(SecurityConsts.OK.value());
}else if(request.getRequestURI().equals("/users/login")){
filterChain.doFilter(request,response);
}
else {
String autorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if(autorizationHeader == null || !autorizationHeader.startsWith(SecurityConsts.TOKEN_PREFIX)){
filterChain.doFilter(request,response);
return;
}
String token = autorizationHeader.substring(SecurityConsts.TOKEN_PREFIX.length());
String username = jwtTokenProvider.getSubject(token);
if(jwtTokenProvider.isTokenValid(username,token) && SecurityContextHolder.getContext().getAuthentication() == null){
List<GrantedAuthority> authorities = jwtTokenProvider.getAuthorities(token);
Authentication authentication = jwtTokenProvider.getAuthentication(username,authorities,request);
SecurityContextHolder.getContext().setAuthentication(authentication);
}else {
SecurityContextHolder.clearContext();
}
}
filterChain.doFilter(request,response);
}
}
跨域配置class
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
.allowedHeaders("*")
.allowedOrigins("*");
}
};
}
}
你能调试应用程序吗?你在评论javax.management.InstanceNotFoundException: org.springframework.boot:type=Admin,name=SpringApplication
中提到的错误是IDE-related,有关于它的Whosebug post,所以这不是问题。
尝试调试代码并检查:
- 如果你真的到达
JWTAuthorizationFilter
- 如果您的用户在此处被识别为管理员用户:
List<GrantedAuthority> authorities = jwtTokenProvider.getAuthorities(token);
- 检查您是否在
Authorization
header 中向 Postman 传递了正确且最新的令牌;检查 url(在您提供的屏幕截图中 url 不正确,它是 useErnames
)
SecurityConsts.TOKEN_PREFIX
的值是多少?它应该包括 space,即 'Bearer '
更新
基于下面关于我的第二个问题的评论(虽然我打算在运行时以调试模式检查值,但无论如何..)
String[] ADMIN_AUTHORITIES = {"ADMIN_ROLE"};
在安全配置中您正在寻找 hasAnyAuthority("ROLE_ADMIN")
,但为您的管理员分配了 "ADMIN_ROLE"
。措辞不同:ROLE_ADMIN 与 ADMIN_ROLE。
当您更新其中之一时,请注意 Spring 在某些情况下会自行将单词“ROLE”添加到权限名称中。所以尝试使用和不使用这个前缀。
我正在做一个简单的项目,它有 2 个定义的角色 Admin 和 User,admin 角色有权通过他们的用户名查看用户,但是当我以管理员身份登录并点击 /admin/users/usernames/{username}
我得到 401 Unauthenticated 你可以在这里看到请求 postman request
以下实现有什么问题?
**登录方式**
@Override
public ResponseEntity<User> login(String username, String password) {
authenticate(username,password);
User loginUser = findUserByUsername(username);
UserPrinciple userPrinciple = new UserPrinciple(loginUser);
HttpHeaders tokenHeader = getJwtToken(userPrinciple);
return new ResponseEntity<>(loginUser,tokenHeader, HttpStatus.OK);
}
private void authenticate(String username, String password) {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,password));
}
private HttpHeaders getJwtToken(UserPrinciple userPrinciple) {
String token= jwtTokenProvider.generateJWTToken(userPrinciple);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(SecurityConsts.JWT_TOKEN_HEADER,token);
return httpHeaders;
}
用户 ReST 控件
@RestController
@RequestMapping("/admin/users")
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/")
public List<User> getUsers() {
return userService.getUsers();
}
@GetMapping("/emails/{email}")
public User findUserByEmail(@PathVariable String email) {
return userService.findUserByEmail(email);
}
@GetMapping("/usernames/{username}")
public User findUserByUsername(@PathVariable String username) {
return userService.findUserByUsername(username);
}
@JsonView(BodyView.BasicUser.class)
@PostMapping("/register")
public User addUser(@RequestBody User user) throws UsernameExistsException {
return userService.addUser(user);
}
@JsonView(BodyView.BasicUser.class)
@PostMapping("/login")
public ResponseEntity<User> login(@RequestBody User user) {
return userService.login(user.getUsername(), user.getPassword());
}
}
POM.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>FM6-App</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>FM6-App</name>
<description>FM6-App</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
网络安全配置器适配器
public 网址:
public static final String[] PUBLIC_URLS ={"/users/admin/login/**","/admin/users/register/**","/demandes/**","/criteres/**"};
@Configuration
@EnableWebSecurity
@ComponentScan
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JWTAuthorizationFilter jwtAuthorizationFilter;
@Autowired
private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JWTAccessDenielHandler jwtAccessDenielHandler;
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/admin/users/login/**").permitAll()
.antMatchers(SecurityConsts.PUBLIC_URLS).permitAll()
.antMatchers("/admin/**").hasAnyAuthority("ROLE_ADMIN")
.antMatchers("/utilisateur/**").hasAnyAuthority("ROLE_USER")
// .anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(jwtAccessDenielHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
}
@Bean(name = "authenticationManager")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
JWT 授权过滤器
@Component
public class JWTAuthorizationFilter extends OncePerRequestFilter {
@Autowired
private JWTTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getMethod().equalsIgnoreCase(SecurityConsts.OPTION_HTTP_METHOD)){
response.setStatus(SecurityConsts.OK.value());
}else if(request.getRequestURI().equals("/users/login")){
filterChain.doFilter(request,response);
}
else {
String autorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if(autorizationHeader == null || !autorizationHeader.startsWith(SecurityConsts.TOKEN_PREFIX)){
filterChain.doFilter(request,response);
return;
}
String token = autorizationHeader.substring(SecurityConsts.TOKEN_PREFIX.length());
String username = jwtTokenProvider.getSubject(token);
if(jwtTokenProvider.isTokenValid(username,token) && SecurityContextHolder.getContext().getAuthentication() == null){
List<GrantedAuthority> authorities = jwtTokenProvider.getAuthorities(token);
Authentication authentication = jwtTokenProvider.getAuthentication(username,authorities,request);
SecurityContextHolder.getContext().setAuthentication(authentication);
}else {
SecurityContextHolder.clearContext();
}
}
filterChain.doFilter(request,response);
}
}
跨域配置class
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
.allowedHeaders("*")
.allowedOrigins("*");
}
};
}
}
你能调试应用程序吗?你在评论javax.management.InstanceNotFoundException: org.springframework.boot:type=Admin,name=SpringApplication
中提到的错误是IDE-related,有关于它的Whosebug post,所以这不是问题。
尝试调试代码并检查:
- 如果你真的到达
JWTAuthorizationFilter
- 如果您的用户在此处被识别为管理员用户:
List<GrantedAuthority> authorities = jwtTokenProvider.getAuthorities(token);
- 检查您是否在
Authorization
header 中向 Postman 传递了正确且最新的令牌;检查 url(在您提供的屏幕截图中 url 不正确,它是useErnames
) SecurityConsts.TOKEN_PREFIX
的值是多少?它应该包括 space,即 'Bearer '
更新 基于下面关于我的第二个问题的评论(虽然我打算在运行时以调试模式检查值,但无论如何..)
String[] ADMIN_AUTHORITIES = {"ADMIN_ROLE"};
在安全配置中您正在寻找 hasAnyAuthority("ROLE_ADMIN")
,但为您的管理员分配了 "ADMIN_ROLE"
。措辞不同:ROLE_ADMIN 与 ADMIN_ROLE。
当您更新其中之一时,请注意 Spring 在某些情况下会自行将单词“ROLE”添加到权限名称中。所以尝试使用和不使用这个前缀。