使用自定义过滤器的另一种方法:使用http.addFilterBefore()
这种解决方案更像是一个框架,帮助您建立基本框架。我创建了一个工作演示
并添加了一些必要的注释以帮助理解该过程。它带有一些简单的基于角色
和基于权限
的身份验证/授权,以及一个公共可访问端点
设置,您可以轻松地选择并使用。
用户类设置:
public class User implements UserDetails {
private final String username;
private final String password;
private final List<? extends GrantedAuthority> grantedAuthorities;
public User(
String username,
String password,
List<? extends GrantedAuthority> grantedAuthorities
) {
this.username = username;
this.password = password;
this.grantedAuthorities = grantedAuthorities;
}
}
通过addFilterBefore()
方法添加自定义过滤器:
http
.authorizeRequests()
.antMatchers("/")
.permitAll()
.addFilterBefore( // Filter login request only
new LoginFilter("login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class
)
.addFilterBefore( // Filter logout request only
new LogoutFilter("logout"),
UsernamePasswordAuthenticationFilter.class
)
.addFilterBefore( // Verify user on every request
new AuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class
);
自定义LoginFilter
继承AbstractAuthenticationProcessingFilter
,并覆盖三个方法以处理身份验证:
public class LoginFilter extends AbstractAuthenticationProcessingFilter {
public LoginFilter(String url, AuthenticationManager authManager) {
super(url, authManager);
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest req,
HttpServletResponse res
)
throws AuthenticationException, IOException {
LoginUserDto loginUserDto = new ObjectMapper()
.readValue(req.getInputStream(), LoginUserDto.class);
return getAuthenticationManager()
.authenticate(
new UsernamePasswordAuthenticationToken(
loginUserDto.getUsername(),
loginUserDto.getPassword()
)
);
}
@Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth
)
throws IOException, ServletException {
User user = (User) auth.getPrincipal();
req.getSession().setAttribute(UserSessionKey, user);
res.getOutputStream().print("You are logged in as " + user.getUsername());
}
@Override
protected void unsuccessfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException failed
)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("text/plain");
response.getOutputStream().print(failed.getMessage());
}
}
自定义 AuthenticationFilter
检查存储在会话中的 auth info
并传递给 SecurityContext
:
public class AuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain filterChain
)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession();
User user = (User) session.getAttribute(UserSessionKey);
if (user != null) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
user,
user.getPassword(),
user.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
filterChain.doFilter(request, response);
}
}
自定义LogoutFilter
非常简单明了,只需使会话无效并终止认证过程:
public class LogoutFilter extends AbstractAuthenticationProcessingFilter {
public LogoutFilter(String url) {
super(url);
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest req,
HttpServletResponse res
)
throws AuthenticationException, IOException {
req.getSession().invalidate();
res.getWriter().println("You logged out!");
return null;
}
}
一点解释:
这三个自定义过滤器的作用是,login
和 logout
过滤器只监听它们各自的端点。
在登录过滤器中,我们从客户端获取用户名和密码
,并针对实际情况检查其是否有效,如果是有效用户,则将其放入会话并传递给SecurityContext
。
在注销过滤器中,我们简单地使会话无效
并返回一个字符串。
而自定义的AuthenticationFilter
将尝试验证每个传入的请求,以从会话中获取用户信息,然后将其传递给SecurityContext
。