案例中有使用mongodb数据库,具体的spring boot整合mongodb的案例参考地址http://www.leftso.com/blog/135.html
Spring Security有一些使用复杂的意见。那当然,当你看的时候,它的范围很复杂,因为它的范围涵盖了大量的用例。事实上,真正的spring的精神,你不必一次使用你所拥有的用例的一切功能。事实上,当你开始使用Spring Boot进行樱桃挑选并将其恢复时,它似乎并不复杂。
我们从使用情况开始,我在想一些比较常见的东西,几乎每个项目都出现一些基本的访问限制。所以,这样一个应用程序的要求可以是:
这样的应用程序的源代码在GitHub上,供您查看。建议您将源代码打开,以下将是某些关键点的评论。啊,和安全相关的东西接近尾声,所以只要知道基础知识就可以向下滚动。
此外,标准的Spring Boot依赖关系,最重要的依赖关系是Spring Security,Spring Data JPA的启动器模块,因为我们需要某处存储用户,而嵌入式内存中的HSQLDB作为存储引擎。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
在现实生活中,您可能会更有兴趣连接到某种外部数据库引擎,例如本文中关于使用BoneCP的数据库连接池的描述。我也使用Freemarker作为模板引擎,但是如果这不是你的事情,那么对于JSP来说,重写它也应该很简单,就像在本文中关于Spring MVC应用程序一样。
所以我们会有这样的User
实体:
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Long id;
@Column(name = "email", nullable = false, unique = true)
private String email;
@Column(name = "password_hash", nullable = false)
private String passwordHash;
@Column(name = "role", nullable = false)
@Enumerated(EnumType.STRING)
private Role role;
// getters, setters
}
如您所见,只有密码的哈希将存储在数据库中,这通常是一个好主意。该email
领域还有一个独特的约束,但它不是主要的关键。由于id
某个原因,电子邮件地址是您不想在访问日志中显示的相当敏感的信息,因此用户被标识,所以我们将尽可能地使用id。
Role
是一个简单的枚举:
public enum Role {
USER, ADMIN
}
除此之外,创建新用户的表单将是很好的:
public class UserCreateForm {
@NotEmpty
private String email = "";
@NotEmpty
private String password = "";
@NotEmpty
private String passwordRepeated = "";
@NotNull
private Role role = Role.USER;
}
这将用作Web层和服务层之间的数据传输对象(DTO)。它由Hibernate Validator验证约束注释,并设置一些合理的默认值。请注意,它与User
对象略有不同,因此我希望将User
实体“泄漏” 到Web层中,我真的不能。
这就是我们现在所需要的。
在服务层,业务逻辑应该是什么,我们需要一些东西来检索User
他的id,电子邮件,列出所有的用户并创建一个新的用户。
所以接口将是:
public interface UserService {
Optional<User> getUserById(long id);
Optional<User> getUserByEmail(String email);
Collection<User> getAllUsers();
User create(UserCreateForm form);
}
服务的实现:
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public Optional<User> getUserById(long id) {
return Optional.ofNullable(userRepository.findOne(id));
}
@Override
public Optional<User> getUserByEmail(String email) {
return userRepository.findOneByEmail(email);
}
@Override
public Collection<User> getAllUsers() {
return userRepository.findAll(new Sort("email"));
}
@Override
public User create(UserCreateForm form) {
User user = new User();
user.setEmail(form.getEmail());
user.setPasswordHash(new BCryptPasswordEncoder().encode(form.getPassword()));
user.setRole(form.getRole());
return userRepository.save(user);
}
}
这里不值得一个评论 - 服务代理到UserRepository
大部分时间。值得注意的是,在该create()
方法中,该表单用于构建一个新User
对象。哈希是从使用的密码BCryptPasswordEncoder
生成的,这应该比臭名昭着的MD5产生更好的哈希。
的UserRepository
定义如下:
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findOneByEmail(String email);
}
这里只添加一个非默认方法findOneByEmail
。请注意,我希望它返回User
包装在JDK8中Optional
,这是Spring的一个新功能,并且使处理null
值更容易。
这是控制者及其观点。为了满足应用的要求,我们至少需要一对夫妇。
我们将处理/
网站的根HomeController
,其中只返回一个home
视图:
@Controller
public class HomeController {
@RequestMapping("/")
public String getHomePage() {
return "home";
}
}
同样,用户的列表将被映射到/users
并由其处理UsersController
。它UserService
注入,要求它返回Collection
的User
对象,将它们放入了users
模型属性,然后调用users
视图名称:
@Controller
public class UsersController {
private final UserService userService;
@Autowired
public UsersController(UserService userService) {
this.userService = userService;
}
@RequestMapping("/users")
public ModelAndView getUsersPage() {
return new ModelAndView("users", "users", userService.getAllUsers());
}
}
接下来,我们需要一个控制器来处理查看和创建一个新的用户,我称之为它UserController
,它更复杂:
@Controller
public class UserController {
private final UserService userService;
private final UserCreateFormValidator userCreateFormValidator;
@Autowired
public UserController(UserService userService, UserCreateFormValidator userCreateFormValidator) {
this.userService = userService;
this.userCreateFormValidator = userCreateFormValidator;
}
@InitBinder("form")
public void initBinder(WebDataBinder binder) {
binder.addValidators(userCreateFormValidator);
}
@RequestMapping("/user/{id}")
public ModelAndView getUserPage(@PathVariable Long id) {
return new ModelAndView("user", "user", userService.getUserById(id)
.orElseThrow(() -> new NoSuchElementException(String.format("User=%s not found", id))));
}
@RequestMapping(value = "/user/create", method = RequestMethod.GET)
public ModelAndView getUserCreatePage() {
return new ModelAndView("user_create", "form", new UserCreateForm());
}
@RequestMapping(value = "/user/create", method = RequestMethod.POST)
public String handleUserCreateForm(@Valid @ModelAttribute("form") UserCreateForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "user_create";
}
try {
userService.create(form);
} catch (DataIntegrityViolationException e) {
bindingResult.reject("email.exists", "Email already exists");
return "user_create";
}
return "redirect:/users";
}
}
视图被映射到/user/{id}
URL,由getUserPage()
方法处理。它要求UserService
一个用户id
,它是从URL中提取的,并作为参数传递给此方法。你记得,UserService.getUserById()
返回一个User
包装的实例Optional
。因此,.orElseThrow()
被要求Optional
获得一个User
实例,或者在它的时候抛出异常null
。
创建一个新User
的映射到/user/create
并由两种方法处理:getUserCreatePage()
和handleUserCreateForm()
。第一个只返回一个user_create
具有空格式的视图作为form
模型的属性。另一个响应POST
请求,并UserCreateForm
作为参数进行验证。如果表单中有错误,则由BindingResult
该视图返回。如果表单确定,则将其传递给UserService.create()
方法。
还有一个额外的检查DataIntegrityViolationException
。如果发生这种情况,则认为是因为尝试User
使用数据库中已经存在的电子邮件地址来创建,因此再次呈现该表单。
在现实生活中,知道哪个约束被严重违反是非常困难的(或者与ORM无关),所以当做这些假设时,至少应该记录异常情况以便进一步检查。应该注意防止这种异常发生在第一位,如对于重复的电子邮件的形式的正确验证。
否则,如果一切正常,则重定向到/users
URL。
UserCreateForm
该@InitBinder
在注解的方法UserController
添加UserCreateFormValidator
到form
参数,告诉它应该由它来验证。这样做的原因在于,UserCreateForm
需要对两种可能的情况进行整体验证:
这样做,UserCreateFormValidator
实现如下:
@Component
public class UserCreateFormValidator implements Validator {
private final UserService userService;
@Autowired
public UserCreateFormValidator(UserService userService) {
this.userService = userService;
}
@Override
public boolean supports(Class<?> clazz) {
return clazz.equals(UserCreateForm.class);
}
@Override
public void validate(Object target, Errors errors) {
UserCreateForm form = (UserCreateForm) target;
validatePasswords(errors, form);
validateEmail(errors, form);
}
private void validatePasswords(Errors errors, UserCreateForm form) {
if (!form.getPassword().equals(form.getPasswordRepeated())) {
errors.reject("password.no_match", "Passwords do not match");
}
}
private void validateEmail(Errors errors, UserCreateForm form) {
if (userService.getUserByEmail(form.getEmail()).isPresent()) {
errors.reject("email.exists", "User with this email already exists");
}
}
}
它将映射到/login
URL并处理LoginController
:
@Controller
public class LoginController {
@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView getLoginPage(@RequestParam Optional<String> error) {
return new ModelAndView("login", "error", error);
}
}
请注意,它只处理GET
请求方法,通过error
在模型中返回具有可选参数的视图。POST
表单的部分和实际处理将由Spring Security完成。
表单的模板如下所示:
<form role="form" action="/login" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<div>
<label for="email">Email address</label>
<input type="email" name="email" id="email" required autofocus>
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
</div>
<div>
<label for="remember-me">Remember me</label>
<input type="checkbox" name="remember-me" id="remember-me">
</div>
<button type="submit">Sign in</button>
</form>
<#if error.isPresent()>
<p>The email or password you have entered is invalid, try again.</p>
</#if>
它具有普通email
,password
输入字段,一个remember-me
复选框和一个提交按钮。如果出现错误,将显示说明认证失败的消息。
这有效地包含了该应用程序功能所需的一切。剩下的是添加一些安全功能。
关于_csrf
上面窗体视图中出现的内容。它是由应用程序生成的用于验证请求的CSRF令牌。这是为了确保表单数据来自您的应用程序,而不是来自其他地方。这是Spring Security的一个功能,默认情况下由Spring Boot启动。
对于JSP和Freemarker,_csrf
变量只是暴露在视图中。它包含一个CsrfToken
对象,该对象具有一个getParameterName()
方法来获取一个CSRF参数名称(默认情况下是这个名称_csrf
)和一个getToken()
获取实际令牌的方法。然后,您可以将其放在一个隐藏的领域以及其余的表单中:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
现在我们有一个应用程序准备好了,是时候设置身份验证。当我们使用我们的登录表单识别那个人,作为现有用户,并且获得足够的信息以授权他们的进一步请求,认证意味着这一部分。
需要添加Spring Security的此配置:
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("email")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
这肯定需要一个解释。
首先要提到的是@Order
注释,它基本上保留了Spring Boot设置的所有默认值,只是在这个文件中覆盖它们。
该configure(HttpSecurity http)
方法是设置实际的基于URL的安全性。我们来看看它在这里做什么
/login
,允许所有。在登录失败时,/login?error
会发生重定向。我们LoginController
映射了这个URL。/logout
允许的。之后,用户将被重定向到/
。这里一个重要的意见是,如果CSRF保护开启,请求/logout
应该是POST
。该configure(AuthenticationManagerBuilder auth)
方法是设置认证机制的地方。它的设置使得认证将被处理UserDetailsService
,哪个实现被注入,并且密码预期被加密BCryptPasswordEncoder
。
值得一提的是还有很多其他的认证方法UserDetailsService
。这只是一种使我们能够使用现有的服务层对象来实现的方法,因此它很适合这个应用。
这UserDetailsService
是Spring Security使用的接口,用于了解用户是否使用登录表单,他们的密码应该是什么,以及系统中有哪些权限。它有一个单一的方法:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
如果用户名存在,则该方法loadUserByUsername()
返回UserDetails
实例,如果不存在则返回实例UsernameNotFoundException
。
我的实现,注入到同一个SecurityConfig
如下:
@Service
public class CurrentUserDetailsService implements UserDetailsService {
private final UserService userService;
@Autowired
public CurrentUserDetailsService(UserService userService) {
this.userService = userService;
}
@Override
public CurrentUser loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userService.getUserByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(String.format("User with email=%s was not found", email)));
return new CurrentUser(user);
}
}
正如你所看到的,只是要求UserService
有一个电子邮件的用户。如果不存在,抛出异常。如果是,CurrentUser
则返回对象。
但是呢CurrentUser
?它应该是UserDetails
。那么,首先,这UserDetails
只是一个接口:
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
它描述了具有用户名,密码,GrantedAuthority
对象列表以及某些不明确标志的用户,这些标志由于各种帐户无效的原因而被描述。
所以我们需要返回一个实现。至少有一个是Spring Security提供的org.springframework.security.core.userdetails.User
。
可以使用它,但棘手的部分是将我们的User
域对象与UserDetails
授权可能需要相关联。它可以通过多种方式完成:
User
域对象UserDetails
直接实现 - 它将允许返回User
完全按照收到的方式UserService
。缺点就是用与Spring Security相关的代码来“污染”域对象。org.springframework.security.core.userdetails.User
,只需将User
实体映射到它。这很好,但是有一些关于用户可用的附加信息,如id
直接访问role
或其他任何东西是很好的。User
对象。最后一个选择是我在这里使用的,所以CurrentUser
:
public class CurrentUser extends org.springframework.security.core.userdetails.User {
private User user;
public CurrentUser(User user) {
super(user.getEmail(), user.getPasswordHash(), AuthorityUtils.createAuthorityList(user.getRole().toString()));
this.user = user;
}
public User getUser() {
return user;
}
public Long getId() {
return user.getId();
}
public Role getRole() {
return user.getRole();
}
}
...扩展org.springframework.security.core.userdetails.User
,实现UserDetails
。此外,它只是包装我们的User
域对象,加入(可选)方便的方法是代理给它(getRole()
,getId()
,等)。
这个映射很简单,就是在构造函数中发生的:
UserDetails.username
从填充User.email
UserDetails.password
从填充User.passwordHash
User.role
转换为String
,由AuthorityUtils
辅助类包装在GrantedAuthority对象中。它将作为列表的唯一元素UserDetails.getAuthorities()
被调用时可用。true
的org.springframework.security.core.userdetails.User
话虽如此,我必须提到,当你传递这样的包裹实体时,要小心,或者直接实现它们UserDetails
。像这样泄漏域对象并不是一件正确的事情。这也可能是有问题的,即如果实体有一些LAZY获取的关联,那么在尝试获取Hibernate会话之外时可能遇到问题。
或者,只需从实体复制足够的信息CurrentUser
。足够这里足以授权用户,而无需为User
实体调用数据库,因为这将增加执行授权的成本。
无论如何,这应该使我们的登录表单工作。总而言之,一步一步地发生的是:
/login
URL。LoginController
返回与登录形式的图。CurrentUserDetailsService.loadUserByUsername()
用户名(在这种情况下为电子邮件)刚刚输入到表单中。CurrentUserDetailsService
从中获取用户UserDetailsService
并返回CurrentUser
。CurrentUser.getPassword()
并将密码哈希与表单中提供的散列密码进行比较。/login
,如果不是,重定向到/login?error
,再次处理LoginController
。CurrentUser
对象(包裹Authentication
)用于授权检查,或者您应该需要它。有时候,对于那些不想输入登录表单的懒惰者,即使他们的会话过期,也可以“记住我”身份验证。这样会造成安全隐患,所以只要建议您使用它。
它的工作原理如下:
remember-me
参数(在此应用程序中有一个复选框)remember-me
。为了启用它,它只需要一些补充SecurityConfig.configure(HttpSecurity http)
,所以它将看起来像这样:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("email")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("remember-me")
.logoutSuccessUrl("/")
.permitAll()
.and()
.rememberMe();
}
注意rememberMe()
到底。它使整个事情。值得注意的是,默认情况下,令牌存储在内存中。这意味着当应用程序重新启动时,令牌将丢失。你可以使它们持久化,即存储在数据库中,但对于这个应用程序来说,这是足够的。
另一项新的事情是deleteCookies()
对logout()
。remember-me
一旦用户从应用程序中注销,就会强制删除该cookie。
授权是一个过程,找出那个已经被授权的人,我们知道他的一切,可以访问应用程序提供的指定资源。
在这个应用程序中,我们将这些信息包含在一个CurrentUser
对象中。从安全的角度来说,重要的是GrantedAuthority
他所拥有的。你记得我们直接从User.role
字段填充,所以他们将是“USER”或“ADMIN”。
现在,根据要求,我们希望所有人都可以访问某些网址,有些则由授权人员管理,还有一些由管理员访问。
它需要一些补充SecurityConfig
:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/public/**").permitAll()
.antMatchers("/users/**").hasAuthority("ADMIN")
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("email")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("remember-me")
.logoutSuccessUrl("/")
.permitAll()
.and()
.rememberMe();
}
新的东西是电话antMatchers()
。这强制执行:
/
模式(此应用中的主页)和/public/**
所有匹配的网址都将被允许。/users/**
模式(此应用程序中的列表视图)将仅允许具有“ADMIN”权限的用户。以上所有步骤都满足了大部分的要求,但是我们只能通过基于URL的授权无法解决。
那是:
这里的问题是,创建新用户的表单被映射到/user/create
。我们可以添加这个URL SecurityConfig
,但是如果我们做出一个习惯,我们最终会在配置文件中使用微型管理URL。我们也可以将其映射到/users/create
这里,这可能在这里是有意义的,但是想到即将/user/{id}/edit
来可能出现的那样。这是很好的离开它,因为它只是为了添加方法级别的授权给现有的方法UserController
。
要使方法级授权工作,@EnableGlobalMethodSecurity(prePostEnabled = true)
需要将注释添加到SecurityConfig
:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class SecurityConfig extends WebSecurityConfigurerAdapter {
// contents as before
}
这使您可以在应用程序的任何层中对公共方法使用两个注释:
@PreAuthorize("expression")
- 如果expression
以Spring表达式语言(Sprint)编写的,它将评估为“true”,方法将被调用。@PostAuthorize("expression")
- 首先调用该方法,当检查失败时,403状态返回给用户。因此,为了解决第一个要求,处理用户创建的方法UserController
需要注释@PreAuthorize
:
@PreAuthorize("hasAuthority('ADMIN')")
@RequestMapping(value = "/user/create", method = RequestMethod.GET)
public ModelAndView getUserCreatePage() {
// contents as before
}
@PreAuthorize("hasAuthority('ADMIN')")
@RequestMapping(value = "/user/create", method = RequestMethod.POST)
public String handleUserCreateForm(@Valid @ModelAttribute("form") UserCreateForm form, BindingResult bindingResult) {
// contents as before
}
该hasAuthority()
规划环境地政司表达是由Spring Security的提供等等,即:
hasAnyAuthority()
或者hasAnyRole()
(“权限”和“角色”是Spring Security lingo中的同义词!) - 检查当前用户是否具有列表中的GrantedAuthority之一。hasAuthority()
或hasRole()
- 如上所述,仅为一个。isAuthenticated()
或isAnonymous()
- 当前用户是否被认证。isRememberMe()
或者isFullyAuthenticated()
- 当前用户是否通过“记住我”令牌进行身份验证。最后的要求是这样的:
换句话说,我们需要一种方法来告诉用户什么时候可以请求/user/{id}
。当用户是“ADMIN”(可以通过基于URL的身份验证来解决),而且当当前用户对自己发出请求时也是如此,这是不能解决的。
这适用于以下方法UserController
:
@RequestMapping("/user/{id}")
public ModelAndView getUserPage(@PathVariable Long id) {
// contents as before
}
我们需要检查当前用户是“管理” 或在id
传递给方法是一样的id
从User
与关联的域对象CurrentUser
。由于CurrentUser
有User
实例包裹,我们有这方面的信息。所以我们可以用至少两种方法解决它:
id
传递给当前用户的方法id
。要做到这一点,我CurrentUserService
用这个界面创建了
public interface CurrentUserService {
boolean canAccessUser(CurrentUser currentUser, Long userId);
}
而这个实现:
@Service
public class CurrentUserServiceImpl implements CurrentUserService {
@Override
public boolean canAccessUser(CurrentUser currentUser, Long userId) {
return currentUser != null
&& (currentUser.getRole() == Role.ADMIN || currentUser.getId().equals(userId));
}
}
现在在@PreAuthorize
注释中使用它,只需放:
@PreAuthorize("@currentUserServiceImpl.canAccessUser(principal, #id)")
@RequestMapping("/user/{id}")
public ModelAndView getUserPage(@PathVariable Long id) {
// contents as before
}
哪个是用于调用具有principal(CurrentUser
)实例的服务的SpEL表达式,并将id
参数传递给方法。
如果安全模型比较简单,这种方法可以正常工作。如果您发现自己写的东西类似于给予和检查多个访问权限,例如每个用户对多个域对象的查看/编辑/删除,那么最好是进入使用Spring Security Domain对象ACL的方向。
这满足了所有的要求,此时应用程序得到了保障。
无论何时需要从Controller或Service访问当前用户,都可以注入Authentication
对象。可以通过调用获取当前的用户实例Authentication.getPrincipal()
。
这适用于构造函数或属性,如下所示:
@Autowired
private Authentication authentication;
void someMethod() {
UserDetails currentUser = (UserDetails) authentication.getPrincipal();
}
此外,这适用于由@RequestMapping
或者注释的控制器方法中的参数@ModelAttribute
:
@RequestMapping("/")
public String getMainPage(Authentication authentication) {
UserDetails currentUser = (UserDetails) authentication.getPrincipal();
// something
}
在这个应用程序中,可以直接转换CurrentUser
,因为它UserDetails
是正在使用的实际实现:
CurrentUser currentUser = (CurrentUser) authentication.getPrincipal();
这样你也可以访问它所包围的域对象。
访问当前身份验证的用户在视图中是有用的,即当为具有一定权限的用户呈现UI的某些元素时。
在JSP中,可以使用安全标签库来完成,如下所示:
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authentication property="principal.username" />
这应该打印当前用户的名字。对于Freemarker,也可以使用该taglib,尽管它需要更多的功能。
您还可以拧紧taglib,并将Authentication
对象作为视图的模型属性传递:
@RequestMapping("/")
public String getMainPage(@ModelProperty("authentication") Authentication authentication) {
return "some_view";
}
但是为什么不为所有的观点,使用@ControllerAdvice
,只是UserDetails
从Authentication
:
@ControllerAdvice
public class CurrentUserControllerAdvice {
@ModelAttribute("currentUser")
public UserDetails getCurrentUser(Authentication authentication) {
return (authentication == null) ? null : (UserDetails) authentication.getPrincipal();
}
}
之后,您可以通过currentUser
所有视图中的属性访问它。
https://www.leftso.com/article/138.html