问题描述
我正在寻找一种解决方案,可以在其中使用Spring Boot安全性从以下方面管理用户访问权限:
总结:在运行时知道组和访问列表的系统。
我对Spring安全性了解不多,因此我一直在网上寻找一些答案,但找不到任何东西。所有代码段均基于静态antMatchers和角色,例如:
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/something_else_instead_of_this").hasRole("ROLE_and_instead_of_this_static_role");
}
(...)
}
是否可以创建您自己的自定义身份验证器之类的方法,该方法基于(1) URL会找到“文章”,因此允许组来访问,并(2)用户登录,因此分配的组 => 将确定我是否可以继续处理请求或立即返回401;)
在此先感谢您的帮助!
解决方法
我认为您不能仅在WebSecurityConfigurerAdapter
中执行此操作,但这是一个类似的设置,它利用了Spring Security的优势,并演示了如何将访问检查添加到控制器方法。
其他pom.xml
依赖项:
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
...
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
...
(仅当您使用Thymeleaf时才使用后者。)
WebSecurityConfigurerAdapter
:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@NoArgsConstructor @Log4j2
public class WebSecurityConfigurerImpl extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Autowired private PasswordEncoder passwordEncoder;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/css/**","/js/**","/images/**","/webjars/**","/webjarsjs");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().permitAll()
.and().formLogin().loginPage("/login").permitAll()
.and().logout().permitAll();
}
}
在Web MVC @Controller
中,任何人都可以阅读文章(无论是否已登录):
@RequestMapping(method = { GET },value = { "/article/{slug}/" })
@PreAuthorize("permitAll()")
public String article(Model model,@PathVariable String slug) {
...
}
但是只有作者可以使用预览功能:
@RequestMapping(method = { GET },value = { "/preview/" })
@PreAuthorize("hasAuthority('AUTHOR')")
public String preview(Model model) {
...
}
@RequestMapping(method = { POST },value = { "/preview/" })
@PreAuthorize("hasAuthority('AUTHOR')")
public String previewPOST(Model model,Principal principal,HttpSession session,HttpServletRequest request,@Valid PreviewForm form,BindingResult result) {
...
}
Thymeleaf模板也支持此功能,如果用户是AUTHOR,则有条件显示菜单。
<li th:ref="navbar-item" sec:authorize="hasAuthority('AUTHOR')">
<button th:text="'Author'"/>
<ul th:ref="navbar-dropdown">
<li><a th:text="'Preview'" th:href="@{/preview/}"/></li>
</ul>
</li>
并处理“登录/注销”菜单以演示其他可用的安全谓词:
<li th:ref="navbar-item" sec:authorize="!isAuthenticated()">
<a th:text="'Login'" th:href="@{/login}"/>
</li>
<li th:ref="navbar-item" sec:authorize="isAuthenticated()">
<button sec:authentication="name"/>
<ul th:ref="navbar-dropdown">
<li><a th:text="'Change Password'" th:href="@{/password}"/></\
li>
<li><a th:text="'Logout'" th:href="@{/logout}"/></li>
</ul>
</li>
(其余的实现细节可能有助于举例说明,但不一定特定于您的问题。具体来说,我建议您在这里使用“动态”组。)
UserDetailsService
实现依赖于JpaRepository
实现并设置用户的授权:
@Service
@NoArgsConstructor @ToString @Log4j2
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired private CredentialRepository credentialRepository;
@Autowired private AuthorRepository authorRepository;
@Autowired private SubscriberRepository subscriberRepository;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = null;
Optional<Credential> credential =
credentialRepository.findById(username);
if (credential.isPresent()) {
HashSet<GrantedAuthority> set = new HashSet<>();
subscriberRepository.findById(username)
.ifPresent(t -> set.add(new SimpleGrantedAuthority("SUBSCRIBER")));
authorRepository.findById(username)
.ifPresent(t -> set.add(new SimpleGrantedAuthority("AUTHOR")));
user = new User(username,credential.get().getPassword(),set);
} else {
throw new UsernameNotFoundException(username);
}
return user;
}
}
还有JpaRepository
之一的示例:
@Repository
@Transactional(readOnly = true)
public interface AuthorRepository extends JpaRepository<Author,String> {
public Optional<Author> findBySlug(String slug);
}