Spring Boot认证,不使用formLogin(),而是使用自定义登录表单。

huangapple go评论113阅读模式
英文:

Spring Boot authentication without formLogin() but with custom login form

问题

我正在使用Spring Boot Security开发博客应用程序。
我的重写configure方法如下:

  1. @Override
  2. protected void configure(HttpSecurity httpSec) throws Exception {
  3. httpSec
  4. .authorizeRequests()
  5. .antMatchers("/users").authenticated()
  6. .antMatchers("/admin", "/db").hasRole("ADMIN")
  7. .antMatchers("/**").permitAll()
  8. //.anyRequest().authenticated()
  9. .and()
  10. .formLogin()
  11. .loginPage("/login").permitAll()
  12. .and()
  13. .logout().logoutSuccessUrl("/login?logout").permitAll();
  14. httpSec.csrf().disable();
  15. httpSec.headers().frameOptions().disable();
  16. }

以及我的自定义登录表单:

  1. <form name="login" th:action="@{/login}" method="post" class="form-signin">
  2. <h1>Please log in!</h1>
  3. <div th:if="${param.error}" class="alert alert-danger">Wrong username and/or password.</div>
  4. <div th:if="${param.logout}" class="alert alert-success">You logged out successfully.</div>
  5. <label for="username">Username</label>
  6. <input type="text" name="username" class="form-control" placeholder="username" required="true"/>
  7. <label for="password">Password</label>
  8. <input type="password" name="password" class="form-control" placeholder="password" required="true"/>
  9. <br/>
  10. <button type="submit" class="btn btn-lg btn-primary btn-block">Log in</button>
  11. <br/>
  12. <a href="/registration" style="color: blue !important">You can register here.</a>
  13. <hr/>
  14. </form>

Spring Security的默认行为是,当我发送一个需要身份验证的URL请求(例如,在我的情况下是/users或/admin)时,它会自动重定向到这个自定义的登录页面。

我想要禁用这种自动重定向。当需要身份验证时,我想要抛出一个自定义的异常(我使用带有@ControllerAdvice注解的单独类来处理),其中包含类似于“您必须登录才能查看此内容”的消息。但是我仍希通过导航菜单手动访问我的自定义登录页面以进行身份验证。

我该如何实现这一点?

到目前为止,我尝试过.formLogin().disable()。通过这种方式,我仍然可以访问我的自定义登录页面,但是当我尝试提交表单时会出现“方法不允许”的错误。但这是合理的,因为th:action="@{/login}"不能将用户名和密码发送到/login。

英文:

I'm working on a blog application with Spring Boot Security.
My overridden configure method looks like:

  1. @Override
  2. protected void configure(HttpSecurity httpSec) throws Exception {
  3. httpSec
  4. .authorizeRequests()
  5. .antMatchers(&quot;/users&quot;).authenticated()
  6. .antMatchers(&quot;/admin&quot;, &quot;/db&quot;).hasRole(&quot;ADMIN&quot;)
  7. .antMatchers(&quot;/**&quot;).permitAll()
  8. //.anyRequest().authenticated()
  9. .and()
  10. .formLogin()
  11. .loginPage(&quot;/login&quot;).permitAll()
  12. .and()
  13. .logout().logoutSuccessUrl(&quot;/login?logout&quot;).permitAll();
  14. httpSec.csrf().disable();
  15. httpSec.headers().frameOptions().disable();
  16. }

And my custom login form:

  1. &lt;form name=&quot;login&quot; th:action=&quot;@{/login}&quot; method=&quot;post&quot; class=&quot;form-signin&quot;&gt;
  2. &lt;h1&gt;Please log in!&lt;/h1&gt;
  3. &lt;div th:if=&quot;${param.error}&quot; class=&quot;alert alert-danger&quot;&gt;Wrong username and/or password.&lt;/div&gt;
  4. &lt;div th:if=&quot;${param.logout}&quot; class=&quot;alert alert-success&quot;&gt;You logged out successfully.&lt;/div&gt;
  5. &lt;label for=&quot;username&quot;&gt;Username&lt;/label&gt;
  6. &lt;input type=&quot;text&quot; name=&quot;username&quot; class=&quot;form-control&quot; placeholder=&quot;username&quot; required=&quot;true&quot;/&gt;
  7. &lt;label for=&quot;password&quot;&gt;Password&lt;/label&gt;
  8. &lt;input type=&quot;password&quot; name=&quot;password&quot; class=&quot;form-control&quot; placeholder=&quot;password&quot; required=&quot;true&quot;/&gt;
  9. &lt;br/&gt;
  10. &lt;button type=&quot;submit&quot; class=&quot;btn btn-lg btn-primary btn-block&quot;&gt;Log in&lt;/button&gt;
  11. &lt;br/&gt;
  12. &lt;a href=&quot;/registration&quot; style=&quot;color: blue !important&quot;&gt;You can register here.&lt;/a&gt;
  13. &lt;hr/&gt;
  14. &lt;/form&gt;

The default behaviour of Spring Security is that when I send a request to an URL which needs to be authenticated (for example /users or /admin in my case) it automatically redirects to this custom login page.

I would like to disable this automatic redirection. When authentication is needed I would like to throw a custom exception (which I handle with a separate Class with @ControllerAdvice annotation) instead with message like "You have to log in to see this content". But I would like to reach my custom login page via navigation menu to authenticate "manually".

How could I reach this?

So far I have tried .formLogin().disabled(). In this way I can still reach my custom login page, but when I try to submit it gave an error "Method not allowed". But it is logical since th:action=&quot;@{/login}&quot; can't send the username and password to /login.

答案1

得分: 1

我找到了一个解决方案。也许不是完美的,但它有效。首先,在我的配置方法中,我已经注释掉了登录/注销部分:

  1. @Override
  2. protected void configure(HttpSecurity httpSec) throws Exception {
  3. httpSec
  4. .authorizeRequests()
  5. .antMatchers("/users").authenticated()
  6. .antMatchers("/admin", "/db").hasRole("ADMIN")
  7. .antMatchers("/**").permitAll()
  8. /**
  9. .anyRequest().authenticated()
  10. .and()
  11. .formLogin()
  12. .loginPage("/login").permitAll()
  13. .and()
  14. .logout().logoutSuccessUrl("/login?logout").permitAll();
  15. */
  16. .and()
  17. .csrf().disable()
  18. .headers().frameOptions().disable();
  19. }

从现在开始,当需要身份验证时,它不会重定向到任何(自定义)登录界面。但是,应该处理禁止访问(403)错误:

  1. case "Forbidden":
  2. if (SecurityContextHolder.getContext().getAuthentication().getAuthorities().toString().equals("[ROLE_ANONYMOUS]"))
  3. error.put("error", "You have to log in to see this content");
  4. else error.put("error", "It is only for admins");
  5. break;

下一步是在导航菜单中创建登录表单:

  1. <form th:action="@{/loginauth}" method="post">
  2. <span sec:authorize="!isAuthenticated()">
  3. <input type="text" name="email" placeholder="e-mail" required="true"/>
  4. <input type="password" name="password" placeholder="password" required="true"/>
  5. <button type="submit" class="btn btn-success btn-xs">Log in</button>
  6. </span>
  7. </form>

提交后,我们必须被转发到一个@Controller类中的@PostMapping方法:

  1. @PostMapping("/loginauth")
  2. public String authenticateLogin(HttpServletRequest request) {
  3. loginService.authenticateBlogUser(request.getParameter("email"), request.getParameter("password"));
  4. return "redirect:/";
  5. }

最后,这些数据应该被发送到服务层进行处理:

  1. BlogUserRepository blogUserRepository;
  2. @Autowired
  3. public void setBlogUserRepository(BlogUserRepository blogUserRepository) {
  4. this.blogUserRepository = blogUserRepository;
  5. }
  6. public void authenticateBlogUser(String email, String password) throws UsernameNotFoundException {
  7. BlogUser user = blogUserRepository.findByEmail(email);
  8. if (user == null || !user.getPassword().equals(password))
  9. throw new UsernameNotFoundException("Wrong e-mail and/or password");
  10. Collection<GrantedAuthority> authorities = new HashSet<>();
  11. Set<Role> roles = user.getRoles();
  12. for (Role role : roles)
  13. authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getAuth()));
  14. SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword(), authorities));
  15. }
英文:

I have found a solution. Maybe not perfect, but it works.
First of all, I have commented out the login/logout part in my configure method:

  1. @Override
  2. protected void configure(HttpSecurity httpSec) throws Exception {
  3. httpSec
  4. .authorizeRequests()
  5. .antMatchers(&quot;/users&quot;).authenticated()
  6. .antMatchers(&quot;/admin&quot;, &quot;/db&quot;).hasRole(&quot;ADMIN&quot;)
  7. .antMatchers(&quot;/**&quot;).permitAll()
  8. /**
  9. .anyRequest().authenticated()
  10. .and()
  11. .formLogin()
  12. .loginPage(&quot;/login&quot;).permitAll()
  13. .and()
  14. .logout().logoutSuccessUrl(&quot;/login?logout&quot;).permitAll();
  15. */
  16. .and()
  17. .csrf().disable()
  18. .headers().frameOptions().disable();
  19. }

From now when authentication is needed it won't redirect to any (custom) login screen. But the Forbidden (403) error should be handled:

  1. case &quot;Forbidden&quot;:
  2. if (SecurityContextHolder.getContext().getAuthentication().getAuthorities().toString().equals(&quot;[ROLE_ANONYMOUS]&quot;))
  3. error.put(&quot;error&quot;, &quot;You have to log in to see this content&quot;);
  4. else error.put(&quot;error&quot;, &quot;It is only for admins&quot;);
  5. break;

Next step is creating a login form into the navigation menu:

  1. &lt;form th:action=&quot;@{/loginauth}&quot; method=&quot;post&quot;&gt;
  2. &lt;span sec:authorize=&quot;!isAuthenticated()&quot;&gt;
  3. &lt;input type=&quot;text&quot; name=&quot;email&quot; placeholder=&quot;e-mail&quot; required=&quot;true&quot;/&gt;
  4. &lt;input type=&quot;password&quot; name=&quot;password&quot; placeholder=&quot;password&quot; required=&quot;true&quot;/&gt;
  5. &lt;button type=&quot;submit&quot; class=&quot;btn btn-success btn-xs&quot;&gt;Log in&lt;/button&gt;
  6. &lt;/span&gt;
  7. &lt;/form&gt;

After submit we have to be forwarded to a @PostMapping in one of the @Controller classes:

  1. @PostMapping(&quot;/loginauth&quot;)
  2. public String authenticateLogin(HttpServletRequest request) {
  3. loginService.authenticateBlogUser(request.getParameter(&quot;email&quot;), request.getParameter(&quot;password&quot;));
  4. return &quot;redirect:/&quot;;
  5. }

Finally this data should be sent to a service layer to process:

  1. BlogUserRepository blogUserRepository;
  2. @Autowired
  3. public void setBlogUserRepository(BlogUserRepository blogUserRepository) {
  4. this.blogUserRepository = blogUserRepository;
  5. }
  6. public void authenticateBlogUser(String email, String password) throws UsernameNotFoundException {
  7. BlogUser user = blogUserRepository.findByEmail(email);
  8. if (user == null || !user.getPassword().equals(password))
  9. throw new UsernameNotFoundException(&quot;Wrong e-mail and/or password&quot;);
  10. Collection&lt;GrantedAuthority&gt; authorities = new HashSet&lt;&gt;();
  11. Set&lt;Role&gt; roles = user.getRoles();
  12. for (Role role : roles)
  13. authorities.add(new SimpleGrantedAuthority(&quot;ROLE_&quot; + role.getAuth()));
  14. SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword(), authorities));
  15. }

huangapple
  • 本文由 发表于 2020年7月28日 22:34:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/63136608.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定