Spring Security 总是返回 HTTP 403 访问被拒绝 [关闭]
Posted
技术标签:
【中文标题】Spring Security 总是返回 HTTP 403 访问被拒绝 [关闭]【英文标题】:Spring security always returns HTTP 403 access denied [closed] 【发布时间】:2022-01-16 23:27:32 【问题描述】:对应用程序进行身份验证后,我在访问应用程序的其他 URL 时遇到问题。我已禁用 csrf,并已添加到 UserDetailsService 类的 loadUserByUsername 方法,但问题仍然存在。我使用 Spring Boot 2.5.6,基本身份验证与 JWT 相结合。
spring 安全配置
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true,jsr250Enabled = true)
@Order(Ordered.HIGHEST_PRECEDENCE)
//@EnableWebSecurity
public class MultiHttpSecurityConfig
@Configuration
@Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter
private final BCryptPasswordEncoder passwordEncoder;
private final KalanblowSystemManagementCustomService customService;
@Autowired
public ApiWebSecurityConfigurationAdapter(BCryptPasswordEncoder passwordEncoder,
@Lazy KalanblowSystemManagementCustomService customService)
super();
this.passwordEncoder = passwordEncoder;
this.customService = customService;
@Override
protected void configure(AuthenticationManagerBuilder authenticationManager) throws Exception
authenticationManager.userDetailsService(customService).passwordEncoder(passwordEncoder);
@Override
protected void configure(HttpSecurity http) throws Exception
http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/v1/user/**").permitAll()
.anyRequest().authenticated().and().exceptionHandling()
.authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED)).and()
.addFilter(new ApiJWTAuthenticationFilter(authenticationManager()))
.addFilter(new ApiJWTAuthorizationFilter(authenticationManager())).sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.httpBasic();
http.csrf().disable();
@Configuration
@Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter
private final BCryptPasswordEncoder passwordEncoder;
private final CustomService customService;
private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
private final JpaPesristentTokenRepository jpersistentTokenRepository;
@Autowired
public FormLoginWebSecurityConfigurerAdapter(BCryptPasswordEncoder passwordEncoder,
@Lazy CustomService customService,
CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler,
JpaPesristentTokenRepository jpersistentTokenRepository
)
super();
this.passwordEncoder = passwordEncoder;
this.customService = customService;
this.customAuthenticationSuccessHandler = customAuthenticationSuccessHandler;
this.jpersistentTokenRepository = jpersistentTokenRepository;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
auth.eraseCredentials(true).userDetailsService(customService).passwordEncoder(passwordEncoder);
@Override
protected void configure(HttpSecurity http) throws Exception
http.httpBasic();
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/images/*").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/users/editeUser/id").hasRole(UserRole.ADMIN.getUserRole())
.antMatchers("/users/list").hasAnyRole(UserRole.ADMIN.getUserRole(), UserRole.STAFF.getUserRole())
.antMatchers("/users/**").hasAnyRole(UserRole.ADMIN.getUserRole(), UserRole.STAFF.getUserRole())
.antMatchers("/user/**").permitAll()
.antMatchers("/admin/**").hasAnyRole(UserRole.ADMIN.getUserRole(), UserRole.STAFF.getUserRole())
.antMatchers("/student/**").hasAnyRole(UserRole.ADMIN.getUserRole(), UserRole.STAFF.getUserRole(),UserRole.STUDENT.getUserRole())
.anyRequest().authenticated()
.and().cors().and()
.formLogin()
.loginPage("/login").permitAll()
.failureUrl("/login?error=true")
.usernameParameter("email")
.passwordParameter("password")
.successHandler(customAuthenticationSuccessHandler)
.and()
.logout().permitAll()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/login").and()
.exceptionHandling();
http.rememberMe().key("remember-me").tokenRepository(jpersistentTokenRepository)
.userDetailsService(customService).tokenValiditySeconds((int) SecurityConstants.EXPIRATION_TIME);
@Override
public void configure(WebSecurity web) throws Exception
web.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**",
"/resources/static/**", "/css/**", "/js/**", "/img/**", "/fonts/**", "/images/**", "/scss/**",
"/vendor/**", "/favicon.ico", "/auth/**", "/favicon.png", "/v2/api-docs", "/configuration/ui",
"/configuration/security", "/webjars/**", "/swagger-resources/**", "/actuator", "/swagger-ui/**",
"/actuator/**", "/swagger-ui/index.html", "/swagger-ui/");
**The class that implements UserDetailsService**
@Transactional
public class CustomService implements UserDetailsService
private final UserService userService;
@Autowired
public CustomService(UserService userService)
super();
this.userService = userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
Optional<UserDto> userDto = Optional.ofNullable(userService.findUserByEmail(username).orElseThrow(
() -> new UsernameNotFoundException(format("Admin with email %s could not be found", username))));
if (userDto != null)
Set<GrantedAuthority> authorities = getAuthority(userDto.get().getRoles());
return buildUserForAuthentication(userDto.get(), authorities);
else
throw new UsernameNotFoundException("Admin with email" + username + "does not exist");
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
private UserDetails buildUserForAuthentication(UserDto userDto, Set<GrantedAuthority> authorities)
return new User(userDto.getEmail(), userDto.getPassword(), enabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, authorities);
private Set<GrantedAuthority> getAuthority(Set<RoleDto> roleDtos)
Set<GrantedAuthority> roles = new HashSet<>();
roleDtos.forEach((role) ->
roles.add(new SimpleGrantedAuthority(role.getUserRoleName()));
);
return new HashSet<>(roles);
**User Controller class**
@Controller
@Slf4j
@RequestMapping("/users")
public class UserController
public static final String EDIT_USER_FORM = "users/editeUser";
public static final String REDIRECT_ADMIN_PAGE_USERS = "redirect:/users/allUsers";
@Autowired
private UserService userService;
private UserFinder userFinder;
private UserSearchErrorResponse userSearchErrorResponse;
@Autowired
private RoleService roleService;
@Autowired
private BCryptPasswordEncoder passworEncoder;
private ModelMapper modelMapper;
/**
* Get all users or search users if searching parameters exist
*
* @param pageable
* @return
*/
@GetMapping("/list")
@PreAuthorize("hasRole('ADMIN')and hasRole('TEACHER') and hasRole('STAFF')")
//@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'ADMIN')")
//@Secured("ADMIN")
public ModelAndView getUsersList(ModelAndView modelAndView, UserSearchParameters userSearchParameters)
modelAndView = new ModelAndView("users/allUsers");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Optional<UserDto> userDto = userService.findUserByEmail(authentication.getName());
// page size
if (userSearchParameters.getPage().isPresent())
Optional<Integer> selectedPageSize = Optional.ofNullable(userSearchParameters.getPageSize().orElse(InitialPagingSizes.INITIAL_PAGE_SIZE));
// Evaluate page size. If requested parameter is null, return initial
Optional<Integer> selectedPage = Optional.ofNullable((userSearchParameters.getPage().orElse(0) < 1) ? InitialPagingSizes.INITIAL_PAGE
: (userSearchParameters.getPage().get() - 1));
PageRequest pageRequest = PageRequest.of(Integer.valueOf(selectedPage.get()),Integer.valueOf( selectedPageSize.get()), Sort.by(Direction.ASC, "id"));
UserSearchResult userSearchResult = new UserSearchResult();
if (userSearchParameters.getPropertyValue().isEmpty()
|| userSearchParameters.getPropertyValue().get().isEmpty())
userSearchResult.setUserPage(userService.listUserByPage(pageRequest));
else
userSearchResult = userFinder.searchUsersByProperty(pageRequest, userSearchParameters);
if (userSearchResult.isNumberFormatException())
return userSearchErrorResponse.respondToNumberFormatException(userSearchResult, modelAndView);
if (userSearchResult.getUserPage().getTotalElements() == 0)
modelAndView = userSearchErrorResponse.respondToEmptySearchResult(modelAndView, pageRequest);
userSearchResult.setUserPage(userService.findAllPageable(pageRequest));
modelAndView.addObject("usersProperty", userSearchParameters.getUsersProperty().get());
modelAndView.addObject("propertyValue", userSearchParameters.getPropertyValue().get());
Pager pager = new Pager(userSearchResult.getUserPage().getTotalPages(),
userSearchResult.getUserPage().getNumber(), InitialPagingSizes.BUTTONS_TO_SHOW,
userSearchResult.getUserPage().getTotalElements());
modelAndView.addObject("pager", pager);
modelAndView.addObject("users", userSearchResult.getUserPage());
modelAndView.addObject("selectedPageSize", selectedPageSize);
modelAndView.addObject("pageSizes", InitialPagingSizes.PAGE_SIZES);
if (userDto.isPresent())
modelAndView.addObject("userName", userDto.get());
modelAndView.addObject("authorithy", userDto.get().getRoles());
return modelAndView;
所有用户的视图
<!DOCTYPE html>
<html lang="fr" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
th:with="activeMenuItem='users'">
<head th:replace="fragments/header :: header">
<title>user</title>
</head>
<body style="background: #4f5050">
<div th:insert="fragments/sidebar :: sidebar"></div>
<!-- ============================================================== -->
<!-- Container fluid -->
<!-- ============================================================== -->
<div class="container-fluid" th:insert="fragments/navigation">
<h1 th:text="#user.all" class="page-titles display-flex-center"></h1>
</div>
<div class="container" id="mainContainer">
<!-- ============================================================== -->
<!-- Bread crumb and right sidebar toggle -->
<!-- ============================================================== -->
<!-- ============================================================== -->
<!-- End Bread crumb and right sidebar toggle -->
<!-- ============================================================== -->
<!-- ============================================================== -->
<!-- Start Page Content -->
<!-- ============================================================== -->
<!-- Row -->
<!-- tag::create-button[] -->
<div
th:replace="fragments/titles :: title-with-button(#user.title, 'user-add', #user.add, @/user/signup)"
sec:authorize="hasRole('ADMIN')"></div>
<!-- end::create-button[] -->
<!-- tag::alert[] -->
<div th:if="$deletedUserName">
<!--.-->
<div
th:replace="fragments/alerts :: success(#user.delete.success($deletedUserName))"></div>
<!--.-->
</div>
<!-- end::alert[] -->
<div>
<!--Search user-->
<div class="row col-lg-12 d-flex flex-nowrap pb-2">
<div class="input-group">
<select class="custom-select" id="search-user-dropdown"
onchange="saveSearchParameters(this);">
<option value="ID">ID</option>
<option value="fullName">fullName</option>
<option value="Email">Email</option>
</select> <input type="text" id="searchUserBar"
onkeyup='saveSearchParameters(this);'
placeholder="Search for user.." class="form-control"
aria-label="Text input with dropdown button">
</div>
<button type="button" class="btn btn-secondary ml-2"
onclick="searchUserByProperty()">Search</button>
</div>
<!--Table with user-->
<div th:if="$users.isEmpty()">[[#no.user]]</div>
<div class="table-responsive .table-responsive"
th:unless="$users.isEmpty()" id="mainContainerRepleace">
<table class="table">
<thead>
<tr>
<th onclick="sortTable(0)" scope="col"
th:replace="fragments/table :: header(#label.user.id)"></th>
<th onclick="sortTable(1)" scope="col"
th:replace="fragments/table :: header(#user.headers.firstName)"></th>
<th onclick="sortTable(2)" scope="col"
th:replace="fragments/table :: header(#user.headers.lastName)"></th>
<th onclick="sortTable(3)" scope="col"
th:replace="fragments/table :: header(#user.headers.email)"></th>
<th onclick="sortTable(4)" scope="col"
th:replace="fragments/table :: header(#user.headers.mobileNumber)"></th>
<th onclick="sortTable(5)" scope="col"
th:replace="fragments/table :: header(#label.user.birthDate)"></th>
<th onclick="sortTable(6)" scope="col"
th:replace="fragments/table :: header(#label.user.password)"></th>
<th onclick="sortTable(7)" scope="col"
th:replace="fragments/table :: header(#label.user.confirmPass)"></th>
<th onclick="sortTable(8)" scope="col"
th:replace="fragments/table :: header(#user.roles)"></th>
<th onclick="sortTable(9)" scope="col"
th:replace="fragments/table :: header(#header.address.list)"></th>
<th scope="col"
th:replace="fragments/table :: header(#user.del)"></th>
<th scope="col"
th:replace="fragments/table :: header(#user.edit)"></th>
</tr>
</thead>
<tbody align="center">
<tr th:each="user : $users" class="table-primary">
<td class="table-primary" scope="col"
th:replace="fragments/table ::data(contents=$user.id,primary=true)"></td>
<td class="
table-secondary" scope="col"
th:replace="fragments/table :: data($user.firstName)"></td>
<td class="table-success" scope="col"
th:replace="fragments/table :: data($user.lastName)"></td>
<td class="table-danger" scope="col"
th:replace="fragments/table :: data($user.email)"></td>
<td class="table-warning" scope="col"
th:replace="fragments/table :: data($user.mobileNumber)"></td>
<td class="table-info" scope="col"
th:replace="fragments/table :: data($user.birthDate)"></td>
<td class="table-light" scope="col"
th:replace="fragments/table :: data($user.password)"></td>
<td class="table-dark" scope="col"
th:replace="fragments/table :: data($user.matchingPassword)"></td>
<td class="table-secondary"
th:replace="fragments/table :: data($user.roles)"></td>
<td>
<p th:each="address : $user.adresse">
<span th:text="$address.street">Street</span> <span
th:text="$address.streetNumber">Street Number</span> <span
th:text="$address.city">City</span> <span
th:text="$address.codePostale">ZIP</span> <span
th:text="$address.state">State</span> <span
th:text="$address.country">country</span>
</p>
</td>
<!-- <td th:switch="$u/editeUser/(id=$user.idser.enabled"><span th:case="true"
style="color: green">Enabled</span> <span th:case="false"
style="color: red">Disabled</span></td> -->
<!--Remove user button-->
<!-- tag::td-admin[] -->
<th:block sec:authorize="hasRole('ADMIN')">
<!--.-->
<td
th:replace="fragments/table :: dataWithLink('Remove user', @'/users/' +'deleteUser/'+$user.id)"><a
id="remove-link" style="text-decoration: none; color: red"
data-toggle='modal' data-target='#deleteModal'
data-placement="right"
th:onclick="'setRowIndexAndUserId(this, ' + $user.id + ')'">
<i class="fa fa-times" aria-hidden="true"></i>
</a></td>
<!--Edit user button-->
<td
th:replace="fragments/table :: dataWithLink('Edit', @'/users/' + 'editeUser/' + $user.id)"><a
style="text-decoration: none; color: blue" class="editBtn"
data-toggle="tooltip" data-placement="right" title="Edit user">
<i class="fa fa-edit"></i>
</a></td>
</th:block>
<!-- end::td-admin[] -->
</tbody>
</table>
<div th:replace="fragments/deleteUserModal :: delete-user-modal"></div>
<!-- <div th:replace="fragments/pagination :: controls(page=$user)"></div> -->
<div class="col-lg-3 pl-0">
<!--Delete success message-->
<div id="alert-messages"></div>
<!--Save success message-->
<div th:if="$userHasBeenSaved"
class="alert alert-success alert-dismissible fade show"
role="alert">
<button type="button" class="close" data-dismiss="alert"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<strong>Well done!</strong> User has been saved!!!
</div>
<!--Update success message-->
<div th:if="$userHasBeenUpdated"
class="alert alert-success alert-dismissible fade show"
role="alert">
<button type="button" class="close" data-dismiss="alert"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<strong>Well done!</strong> User has been updated!!!
</div>
<!--Number format exception message-->
<div th:if="$numberFormatException"
class="alert alert-danger alert-dismissible fade show"
role="alert">
<button type="button" class="close" data-dismiss="alert"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
Please enter a valid number
</div>
<!--No matches found message-->
<div th:if="$noMatches"
class='alert alert-info alert-dismissible fade show' role='alert'>
<button type='button' class='close' data-dismiss='alert'
aria-label='Close'>
<span aria-hidden='true'>×</span>
</button>
Sorry, no matches found for <span th:text="$userProperty"></span>
= <span th:text="$propertyValue"></span>
</div>
</div>
</div>
<a href="#" th:href="@/users/signup"
sec:authorize="hasRole('ADMIN')">>
<button type="button" class="btn btn-primary">New User</button>
</a>
<!--Paging-->
<div id="paging-section" class="row" style="margin-top: 10px;">
<!--Page size dropdown-->
<div class="form-group col-md-1" th:if="$users.totalPages != 0">
<!--Get pageSizesToShow-->
<div hidden id="pageSizesToShow"
th:attr="data-pageSizesToShow = $pager.pageSizesToShowInJSON"></div>
<select class="form-control pagination" id="pageSizeSelect">
<option th:each="pageSize : $pageSizes" th:text="$pageSize"
th:value="$pageSize"
th:selected="$pageSize == $selectedPageSize"></option>
</select>
</div>
<!--Pages-->
<nav aria-label="Page navigation example"
class="form-group col-md-11 pagination-centered">
<ul class="pagination" th:if="$users.totalPages != 0">
<li th:class="$users.number == 0 ? 'page-item disabled'"
class="page-item"><a
th:if="$not #strings.isEmpty(propertyValue)" class="page-link"
th:href="@/users(userProperty=$userProperty,
propertyValue=$propertyValue, pageSize=$selectedPageSize, page=1)">
« </a> <a th:if="$#strings.isEmpty(propertyValue)"
class="page-link"
th:href="@/users/user(pageSize=$selectedPageSize, page=1)">
« </a></li>
<li th:class="$users.number == 0 ? 'page-item disabled'"
class="page-item"><a
th:if="$not #strings.isEmpty(propertyValue) " class="page-link"
th:href="@/users(userProperty=$userProperty,
propertyValue=$propertyValue, pageSize=$selectedPageSize, page=$users.number)">
← </a> <a th:if="$#strings.isEmpty(propertyValue)"
class="page-link"
th:href="@/users/user(pageSize=$selectedPageSize, page=$user.number)">
← </a></li>
<li
th:class="$users.number == (page - 1) ? 'active pointer-disabled'"
class="page-item"
th:each="page : $#numbers.sequence(pager.startPage, pager.endPage)">
<a th:if="$not #strings.isEmpty(propertyValue)"
class="page-link"
th:href="@/users(userProperty=$userProperty,
propertyValue=$propertyValue, pageSize=$selectedPageSize, page=$page)"
th:text="$page"> </a> <a
th:if="$#strings.isEmpty(propertyValue)" class="page-link"
th:href="@/users(pageSize=$selectedPageSize, page=$page)"
th:text="$page"> </a>
</li>
<li
th:class="$users.number + 1 == users.totalPages ? 'page-item disabled'"
class="page-item"><a
th:if="$not #strings.isEmpty(propertyValue)" class="page-link"
th:href="@/users(userProperty=$userProperty,
propertyValue=$propertyValue, pageSize=$selectedPageSize, page=$users.number + 2)">
→ </a> <a th:if="$#strings.isEmpty(propertyValue)"
class="page-link"
th:href="@/users(pageSize=$selectedPageSize, page=$users.number + 2)">
→ </a></li>
<li
th:class="$users.number + 1 == users.totalPages ? 'page-item disabled'"
class="page-item"><a
th:if="$not #strings.isEmpty(propertyValue) " class="page-link"
th:href="@/users(userProperty=$userProperty, propertyValue=$propertyValue,
pageSize=$selectedPageSize, page=$users.totalPages)">
» </a> <a th:if="$#strings.isEmpty(propertyValue)"
class="page-link"
th:href="@/users(pageSize=$selectedPageSize, page=$users.totalPages)">
» </a></li>
</ul>
</nav>
</div>
</div>
</div>
<div th:replace="~fragments/footer :: footer"></div>
</body>
</html>
拜托,你能帮帮我吗?
【问题讨论】:
UserRole.ADMIN.getUserRole()==ADMIN 和 UserRole.STAFF.getUserRole()== STAFF 然后添加 Spring Security 日志,它包含原因。首先你必须改变你的日志配置才能看到 Spring Security 的日志。 我添加了日志,但我没有理由拒绝访问 url。 您确定用户必须拥有所有 3 个角色吗?@PreAuthorize("hasRole('ADMIN')and hasRole('TEACHER') and hasRole('STAFF')")
您遇到的问题是哪个网址,哪个有效?
您是否在日志中修复了 NullPointerException?显示您的新日志。
【参考方案1】:
您确定用户必须在此处拥有所有 3 个角色吗? @PreAuthorize("hasRole('ADMIN')and hasRole('TEACHER') and hasRole('STAFF')")
您遇到的问题是哪个 URL,哪个有效?
在你的配置中你说:
.antMatchers("/users/**").hasAnyRole(UserRole.ADMIN.getUserRole(), UserRole.STAFF.getUserRole())
但在您的 @PreAuthorize
中,您要求用户拥有所有 3 个角色
【讨论】:
我有超过 3 个角色,但根据 url,我有一些角色可以访问或不可以访问,这就是为什么在 localhost:8080/users/list 只有 ADMIN 和 STAFF 可以访问它们 不读取 ADMIN 和 STAFF,读取:同时具有所有 ADMIN 和 STAFF 以及 TEACHER 角色的用户。您的用户是否拥有所有这 3 个角色? 我有一个角色层次结构 ADMIN>STAFF>TEACHER>USER 我的用户具有访问 url 的 ADMIN 角色。 您好 ACV,非常感谢您的关注,我解决了我的 nullpointer 异常,这是一个日志错误:] osswaiFilterSecurityInterceptor : 无法授权过滤器调用 [GET /users/list] 属性 [hasAnyRole( 'ROLE_ADMIN')] 2021-12-16 11:36:44.249 调试 381671 --- [http-nio-8080-exec-5] osswaccess.AccessDeniedHandlerImpl :响应 403 状态码以上是关于Spring Security 总是返回 HTTP 403 访问被拒绝 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
Spring Security + LDAP 总是返回 BadCredentialsException
登录后 Spring Security 总是返回 403 accessDeniedPage [重复]
Spring Security Granted Authorities 总是返回空
Spring Security 总是返回 403 被禁止,访问被拒绝
在 Spring Security 5.1.6 中,logout-success-url 总是重定向到 HTTP 而不是 HTTPS