对于Security默认使用表单认证,而表单认证则使用的是UsernamePasswordAuthenticationFilter过滤器。这篇文章主要是用于自定义认证。
AuthenticationManager默认使用ProviderManager,而ProviderManager的AuthenticationProvider默认DaoAuthenticationProvider,这篇文章就是重写DaoAuthenticationProvider。
1.Authentication类MyUsernamePasswordAuthenticationToken
新建一个Authentication类,用于在认证过程中存放认证信息。
public class MyUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
public MyUsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
2.新建过滤器MyFilter
新建的过滤器继承AbstractAuthenticationProcessingFilter类
public class MyFilter extends AbstractAuthenticationProcessingFilter {
public MyFilter() {
super(new AntPathRequestMatcher("/mylogin", "GET"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if (!request.getMethod().equals("GET")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = request.getParameter("username");
username = username != null ? username.trim() : "";
String password = request.getParameter("password");
password = password != null ? password : "";
MyUsernamePasswordAuthenticationToken authRequest = new MyUsernamePasswordAuthenticationToken(username, password, Collections.emptyList());
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
}
3. UserDetailsService接口实现类的实现
主要用户查询用户信息
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
Map<String, String> user = new HashMap();
Map<String, List<String>> userRole = new HashMap<>();
{
user.put("ls", "123456");
user.put("zs", "111111");
//hasRole 的处理逻辑和 hasAuthority 似乎是一样的,只是hasRole 这
// 里会自动给传入的字符串前缀(默认是ROLE_ ),
// 使用 hasAuthority 更具有一致性,不用考虑要不要加 ROLE_ 前缀,
// 在UserDetailsService类的loadUserByUsername中查询权限,也不需要手动增加。
// 在SecurityExpressionRoot 类中hasAuthority 和 hasRole
// 最终都是调用了 hasAnyAuthorityName 方法。
userRole.put("ls", Arrays.asList("ROLE_admin"));
userRole.put("zs", Arrays.asList("query"));
}
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("MyUserDetailsService.loadUserByUsername 开始");
if (!user.containsKey(username)) {
throw new UsernameNotFoundException("username is not exists");
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (userRole.containsKey(username)) {
authorities = userRole.get(username).stream().map(row -> new SimpleGrantedAuthority(row))
.collect(Collectors.toList());
}
return new User(username, passwordEncoder.encode(user.get(username)), authorities);
}
}
4. 认证核心代码类MyAuthenticationProvider
MyAuthenticationProvider类主要用于用户的登录认证
@Slf4j
public class MyAuthenticationProvider implements AuthenticationProvider {
@Autowired
UserDetailsService myUserDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//获取输入用户名
String username = authentication.getName();
//获取输入明文密码
String password = (String) authentication.getCredentials();
//根据用户名查询匹配用户
User user = (User) myUserDetailsService.loadUserByUsername(username);
//判断用户账户状态
if (!user.isEnabled()) {
throw new DisabledException("该账户已被禁用,请联系管理员");
} else if (!user.isAccountNonLocked()) {
throw new LockedException("该账号已被锁定");
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException("该账号已过期,请联系管理员");
} else if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException("该账户的登录凭证已过期,请重新登录");
}
//验证密码
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("输入密码错误!");
}
return new MyUsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
log.info("supports class:" + authentication.getName());
// return authentication.equals(MyUsernamePasswordAuthenticationToken.class);
return true;
}
}
5. SpringSecurity核心配置
继承自WebSecurityConfigurerAdapter,作用为开启自定义配置,登录成功处理器、失败处理器等,定义规则什么资源拦截,什么可以直接访问等,即上面进行的操作,都要在这个类配置开启才能生效。
- 方式1: 登录方式可以直接使用/mylogin.html界面登录,它可以触发UsernamePasswordAuthenticationFilter过滤器进行认证。
- 方式2: 使用GET方式调用mylogin接口,触发MyFilter进行认证。
- 退出登录使用logout接口
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyAuthenticationProvider myAuthenticationProvider;
/**
* formLogin() 默认使用了UsernamePasswordAuthenticationFilter
* 默认使用了DaoAuthenticationProvider认证逻辑,使用configure可以配置UserDetailsService
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//忽略hellopost接口
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/index", "/loginalert", "/mylogin").permitAll()
.antMatchers("/helloadmin").hasRole("admin")
.antMatchers("/hellouser").hasAuthority("query")
.anyRequest().authenticated()
// 方式1: 默认使用页面登录
.and().formLogin().loginPage("/mylogin.html")
.usernameParameter("uname").passwordParameter("passwd")
.permitAll()
.loginProcessingUrl("/doLogin")
.failureHandler((request, response, exception) -> {
String message = "login fail:" + exception.getMessage();
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("<script>alert('" + message + "');window.location.href='/mylogin.html'</script>");
}).successHandler(((request, response, authentication) -> {
String message = "login success:" + authentication.getName();
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("<script>alert('" + message + "');window.location.href='/index'</script>");
}));
//权限失败
http.exceptionHandling().accessDeniedHandler(((request, response, accessDeniedException) -> {
String message = "access fail:" + accessDeniedException.getMessage();
response.setContentType("text/html");
response.getWriter().write("<script>alert('" + message + "');window.location.href='/index'</script>");
}));
http.authenticationManager(authenticationManager());
http.addFilterBefore(myFilter(), UsernamePasswordAuthenticationFilter.class);
}
// //MyAuthenticationProvider中使用了UserDetailsService,所以不需要设置
// @Autowired
// UserDetailsService myUserDetailsService;
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// //auth.userDetailsService(myUserDetailsService);
// }
/**
* 设置UsernamePasswordAuthenticationFilter的setAuthenticationManager
*
* @return
* @throws Exception
*/
public AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(myAuthenticationProvider);
}
/**
* 方式2: 自定义过滤器登录
*
* @return
* @throws Exception
*/
@Bean
public MyCustomizeLoginFilter myFilter() throws Exception {
MyCustomizeLoginFilter filter = new MyCustomizeLoginFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(((request, response, authentication) -> {
String message = "myfilter login success:" + authentication.getName();
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("<script>alert('" + message + "');window.location.href='/index'</script>");
}));
filter.setAuthenticationFailureHandler((request, response, exception) -> {
String message = "myfilter login fail:" + exception.getMessage();
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("<script>alert('" + message + "');window.location.href='/mylogin.html'</script>");
});
return filter;
}
}
配置类:
@Configuration
public class MyConfig {
@Bean
PasswordEncoder bcryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
UserDetailsService myUserDetailsService() {
return new MyUserDetailsService();
}
@Bean
MyAuthenticationProvider myAuthenticationProvider() {
return new MyAuthenticationProvider();
}
}
6.测试验证
- 方式1: mylogin.html登录触发UsernamePasswordAuthenticationFilter过滤器
- 方式2: mylogin接口,触发MyFilter过滤器