java 使用 Vaadin 登录的 Spring Boot 安全性
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35912404/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Boot Security with Vaadin Login
提问by J. S.
I try to build an application based on Spring Boot (1.2.7.RELEASE) and Vaadin (7.6.3). My problem is that I'm not able to integrate Spring Security with Vaadin. I want a custom Vaadin built LoginScreen and Spring Security control. My project setup is as follows:
我尝试构建基于 Spring Boot (1.2.7.RELEASE) 和 Vaadin (7.6.3) 的应用程序。我的问题是我无法将 Spring Security 与 Vaadin 集成。我想要一个自定义的 Vaadin 内置 LoginScreen 和 Spring Security 控件。我的项目设置如下:
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
.and().authorizeRequests()
.antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
.antMatchers("/authorized", "/**").fullyAuthenticated();
}
}
And here is my Vaadin login UI
这是我的 Vaadin 登录用户界面
@SpringUI(path = "/login")
@Title("LoginPage")
@Theme("valo")
public class LoginUI extends UI {
TextField user;
PasswordField password;
Button loginButton = new Button("Login", this::loginButtonClick);
private static final String username = "username";
private static final String passwordValue = "test123";
@Override
protected void init(VaadinRequest request) {
setSizeFull();
user = new TextField("User:");
user.setWidth("300px");
user.setRequired(true);
user.setInputPrompt("Your username");
password = new PasswordField("Password:");
password.setWidth("300px");
password.setRequired(true);
password.setValue("");
password.setNullRepresentation("");
VerticalLayout fields = new VerticalLayout(user, password, loginButton);
fields.setCaption("Please login to access the application");
fields.setSpacing(true);
fields.setMargin(new MarginInfo(true, true, true, false));
fields.setSizeUndefined();
VerticalLayout uiLayout = new VerticalLayout(fields);
uiLayout.setSizeFull();
uiLayout.setComponentAlignment(fields, Alignment.MIDDLE_CENTER);
setStyleName(Reindeer.LAYOUT_BLUE);
setFocusedComponent(user);
setContent(uiLayout);
}
public void loginButtonClick(Button.ClickEvent e) {
//authorize/authenticate user
//tell spring that my user is authenticated and dispatch to my mainUI
}
}
When I start my application spring redirects me to my login UI, which is fine.
当我启动我的应用程序时,spring 将我重定向到我的登录 UI,这很好。
But I don't know how to authenticate the user against the spring security mechanism and dispatch to my mainUI.
但我不知道如何根据 spring 安全机制对用户进行身份验证并分派到我的 mainUI。
I'm also facing the problem with csrf tokens, if I don't disable csrf I'll get the crfs token is null exception. I found a lot of examples handling those problems but there is no solution provided with Vaadin.
我也面临 csrf 令牌的问题,如果我不禁用 csrf,我将得到 crfs 令牌为空异常。我找到了很多处理这些问题的例子,但 Vaadin 没有提供解决方案。
Thanks for help.
感谢帮助。
回答by J. S.
After a week of struggle and research, I was able to get this working. It was very exhausting because there a lot of information and solutions on the internet, most of them using xml based configuration or JSP form based login, until now I couldn't find another solution without a xml config file using the Vaadin framework to create a custom login page.
经过一周的努力和研究,我能够让它发挥作用。很累,因为网上有很多资料和解决方案,大部分都是使用基于xml的配置或基于JSP表单的登录,直到现在我找不到没有xml配置文件的另一种解决方案,使用Vaadin框架创建一个自定义登录页面。
I cannot guarantee that this is best practice or the easiest solution. Moreover I didn't evaluate every part of it, the login mechanism works as far as I can see but maybe there could be some problems which I haven't discovered yet.
我不能保证这是最佳实践或最简单的解决方案。此外,我没有评估它的每一部分,就我所见,登录机制是有效的,但可能存在一些我尚未发现的问题。
Maybe it'll help someone who face the same problem so I'll post my answer here.
也许它会帮助面临同样问题的人,所以我会在这里发布我的答案。
First of all my securityConfig:
首先我的安全配置:
@Resource(name = "authService")
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
.and().authorizeRequests()
.antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
.antMatchers("/authorized", "/**").fullyAuthenticated();
}
@Bean
public DaoAuthenticationProvider createDaoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
You have to disable crsf but that's no problem since vaadin has its own crsf protection.
您必须禁用 crsf,但这没问题,因为 vaadin 有自己的 crsf 保护。
Furthermore you need to permit some URIs so vaadin can access its resources: /VAADIN/**
is absolutely necessary, I would also reccommend to allow /vaadinServlet/**
, /PUSH/**
and /HEARTBEAT/**
, but it depends on what parts of Vaadin you use.
此外,您需要允许一些 URI,以便 vaadin 可以访问其资源:/VAADIN/**
绝对必要,我还建议允许/vaadinServlet/**
,/PUSH/**
和/HEARTBEAT/**
,但这取决于您使用 Vaadin 的哪些部分。
Second my UserDetailsService
:
其次我的UserDetailsService
:
@Service("authService")
public class AuthService implements UserDetailsService {
@Autowired
CustomUserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
CustomUser user = userRepository.findCustomUserByUserName(userName);
return user;
}
}
The DaoAuthenticationProvider
uses the UserDetails
' loadUserByUserName
method to get an object of a class which implements the UserDetails
interface. Be aware that every attribute described in the UserDetailsInterface
must not be null, otherwise you get a NullPointerException
thrown by the DaoAuthenticationProvider
later.
在DaoAuthenticationProvider
使用UserDetails
“loadUserByUserName
方法得到一个类,它实现的目的UserDetails
接口。请注意, 中描述的每个属性都UserDetailsInterface
不能为空,否则会NullPointerException
被DaoAuthenticationProvider
后者抛出。
I created a JPA Entity which implements the UserDetails interface:
我创建了一个实现 UserDetails 接口的 JPA 实体:
@Entity
public class CustomUser implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@ManyToMany(fetch = FetchType.EAGER)
Collection<Authorities> authorities;
String password;
String userName;
Boolean accountNonExpired;
Boolean accountNonLocked;
Boolean credentialsNonExpired;
Boolean enabled;
@Autowired
@Transient
BCryptPasswordEncoder passwordEncoder;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return userName;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
public void setId(Long id) {
this.id = id;
}
public void setAuthorities(Collection<Authorities> authorities) {
this.authorities = authorities;
}
public void setPassword(String password) {
this.password = passwordEncoder.encode(password);
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setAccountNonExpired(Boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public void setAccountNonLocked(Boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
Plus the Authorities Entity:
加上当局实体:
@Entity
public class Authorities implements GrantedAuthority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String authority;
@Override
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
}
Obviously you'll have to store some user data in the database first before the authentication will work.
显然,您必须先在数据库中存储一些用户数据,然后才能进行身份验证。
In Vaadin I couldn't get it worked by using one UI with different views, so I ended up using two UIs one for login and another for the main application.
在 Vaadin 中,我无法通过使用具有不同视图的 UI 来实现它,因此我最终使用了两个 UI,一个用于登录,另一个用于主应用程序。
In Vaadin I could set the URI path in the class annotation:
在 Vaadin 中,我可以在类注释中设置 URI 路径:
@SpringUI(path = "/login")
@Title("LoginPage")
@Theme("valo")
public class LoginUI extends UI {
//...
}
With this configuration my login screen is available at localhost:port/login
and my main application at localhost:port/main
.
有了这个配置,我的登录屏幕localhost:port/login
和我的主应用程序都可用localhost:port/main
。
I login the user programmatically within a button.click method in my loginUI:
我在 loginUI 的 button.click 方法中以编程方式登录用户:
Authentication auth = new UsernamePasswordAuthenticationToken(userName.getValue(),password.getValue());
Authentication authenticated = daoAuthenticationProvider.authenticate(auth);
SecurityContextHolder.getContext().setAuthentication(authenticated);
//redirect to main application
getPage().setLocation("/main");
I hope it helped some of you.
我希望它对你们中的一些人有所帮助。
回答by Roland Ewald
As an alternative to the given approach, you can also rely on vaadin4spring
, an 'unofficial' set of extensions to further integrate Vaadin and Spring.
作为给定方法的替代方案,您还可以依靠vaadin4spring
一组“非官方”扩展来进一步集成 Vaadin 和 Spring。
It currently offers two waysto integrate Spring Security, which seem to work fine (even if they do not work for you, the code samples should give you enough insights to write custom integration code).
它目前提供了两种集成 Spring Security 的方法,它们似乎工作得很好(即使它们对您不起作用,代码示例也应该为您提供足够的洞察力来编写自定义集成代码)。