以下配置基于表单登录配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
// 自定义页面路径
.loginPage("/api/login")
.and()
.authorizeRequests()
// 允许/api/login的URL访问 否则浏览器页面将提示重定向次数过多进入死循环
.antMatchers("/api/login").permitAll()
.anyRequest()
.authenticated();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/api/login")
// 自定义登录路径
.loginProcessingUrl("/api/formlogin")
.and()
.authorizeRequests()
.antMatchers("/api/login").permitAll()
.anyRequest()
.authenticated();
}
Spring Security 默认表单登录
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
可设置loginProcessingUrl属性来替换默认登录地址
当用户登录成功后需要保存用户登录数据,比如IP地址,登录时间等
// 注入SecurityLoginSuccessHandler
@Autowired
private SecurityLoginSuccessHandler securityLoginSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/api/login")
// 自定义登录成功处理
.successHandler(securityLoginSuccessHandler)
.loginProcessingUrl("/api/formlogin")
.and()
.authorizeRequests()
.antMatchers("/api/login","/static/*").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
SecurityLoginSuccessHandler
@Component
public class SecurityLoginSuccessHandler implements AuthenticationSuccessHandler {
// 重定向跳转类
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
// 缓存请求路径,可获取拦截前的请求路径
private RequestCache requestCache = new HttpSessionRequestCache();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("登录成功");
// 获取拦截前请求路径 并通过RedirectStrategy进行重定向跳转
SavedRequest savedRequest = requestCache.getRequest(request, response);
String redirectUrl = savedRequest.getRedirectUrl();
// todo do something
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
}
}
跟登录成功处理一样 只是实现接口不同而已
@Autowired
private SecurityLoginFailHandler securityLoginFailHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/api/login")
.successHandler(securityLoginSuccessHandler)
// 自定义登录失败处理
.failureHandler(securityLoginFailHandler)
.loginProcessingUrl("/api/formlogin")
.and()
.authorizeRequests()
.antMatchers("/api/login", "/static/*").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
SecurityLoginFailHandler
@Component
public class SecurityLoginFailHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println("登录失败");
// todo do something
response.setCharacterEncoding("UTF-8");
response.setContentType("text/json;charset=UTF-8");
response.getWriter().write(exception.getMessage());
}
}
默认是访问URL/logout
将注销登陆的用户
自定义配置
@Autowired
private SecurityLoginoutHandler securityLoginoutHandler;
@Autowired
private SecurityLoginoutSuccessHandler securityLoginoutSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/api/login")
.successHandler(securityLoginSuccessHandler)
.failureHandler(securityLoginFailHandler)
.loginProcessingUrl("/api/formlogin")
.and()
.authorizeRequests()
.antMatchers("/api/login", "/static/*").permitAll()
.anyRequest()
.authenticated()
.and()
.logout()
// 退出登录地址
.logoutUrl("/logout")
// 退出跳转路径
.logoutSuccessUrl("/api/login")
// 默认session失效
.invalidateHttpSession(true)
// 退出登录处理
.addLogoutHandler(securityLoginoutHandler)
// 退出登录成功处理
.logoutSuccessHandler(securityLoginoutSuccessHandler)
// 指定退出登录后需要删除的cookie名称,多个cookie之间以逗号分隔。
// .deleteCookies(cookieNamesToClear)
.and()
.csrf().disable();
}
SecurityLoginoutHandler
用来执行必要的清理,因而他们不应该抛出错误
@Component
public class SecurityLoginoutHandler implements LogoutHandler {
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
System.out.println("退出登录");
}
}
SecurityLoginoutSuccessHandler
被LogoutFilter在成功注销后调用,用来进行重定向或者转发相应的目的地。注意这个接口与LogoutHandler几乎一样,但是可以抛出异常。
@Component
public class SecurityLoginoutSuccessHandler implements LogoutSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("退出登录成功执行");
this.redirectStrategy.sendRedirect(request, response, "/");
}
}
WebSecurityConfig
@Autowired
private SecurityUserDetailService securityUserDetailService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// 在内存中写死用户数据
auth.inMemoryAuthentication().withUser("123456").password("123456").roles("USER");
// 动态获取用户数据
auth.userDetailsService(securityUserDetailService);
}
SecurityUserDetailService
/**
* 自定义UserDetailService
*
* @author Peng
*/
@Component
public class SecurityUserDetailService implements UserDetailsService {
/**
* String password; 密码
* String username; 账户名
* Set authorities; 角色名
* boolean accountNonExpired; 账户没有过期
* boolean accountNonLocked; 帐号没有被锁定
* boolean credentialsNonExpired; 密码没有过期
* boolean enabled; 是否可用
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 这里写死的密码 实际应用中应该从数据库中获取
User user = new User(username, "123456", true,
true,
true,
true,
AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
return user;
}
}
WebSecurityConfig
@Autowired
private SecurityUserDetailService securityUserDetailService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// 在内存中写死用户数据
auth.inMemoryAuthentication().withUser("123456").password("123456").roles("USER");
// 动态获取用户数据 使用Security加密
auth.userDetailsService(securityUserDetailService).passwordEncoder(new BCryptPasswordEncoder());
// 配置验证方式为加密验证
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(securityUserDetailService);
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
auth.authenticationProvider(authenticationProvider);
}
SecurityUserDetailService
/**
* 自定义UserDetailService
*
* @author Peng
*/
@Component
public class SecurityUserDetailService implements UserDetailsService {
/**
* String password; 密码
* String username; 账户名
* Set authorities; 角色名
* boolean accountNonExpired; 账户没有过期
* boolean accountNonLocked; 帐号没有被锁定
* boolean credentialsNonExpired; 密码没有过期
* boolean enabled; 是否可用
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 这里写死的密码 实际应用中应该从数据库中获取
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// 数据库创建用户数据时密码应通过Security加密后保存
String password = encoder.encode("123456");
System.out.println(password);
User user = new User(username, password, true,
true,
true,
true,
AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
return user;
}
}
PasswordEncoder
// 密码加密
String encode(CharSequence rawPassword);
// 验证密码是否吻合
boolean matches(CharSequence rawPassword, String encodedPassword);
/**
* 获取登录用户数据
*/
@RequestMapping("/api/getme")
@ResponseBody
public Object getLoginUser(Authentication authentication) {
Map<String, Object> json = new HashMap<>();
// 两种方法
json.put("data", SecurityContextHolder.getContext().getAuthentication());
json.put("data1", authentication);
return json;
}
WebSecurityConfig
.antMatchers("/api/me").hasRole("ADMIN")
这样只要是访问“api/me”的路径就会验证用户身份,用户身份必须是ADMIN才允许访问
表达 | 描述 |
---|---|
hasRole([role]) | 如果当前主体具有指定的角色,则返回true. |
hasAnyRole([role1,role2]) | 如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true |
hasAuthority([authority]) | 如果当前的主体具有指定的权限,则返回 true. |
hasAnyAuthority([authority1,authority2]) | 如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true. |
principal | 允许直接访问表示当前用户的主对象 |
permitAll | 允许所有用户访问 |
denyAll | 不允许用户访问 |
isAnonymous() | 如果当前的主体是一个匿名用户,则返回true. |
isRememberMe() | 如果当前的主体是一个匿名用户,则返回true |
isAuthenticated() | 如果用户不是匿名的,则返回 true |
isFullyAuthenticated() | 如果用户不是一个匿名的或是一个记住我的用户返回true |
hasPermission(Object target, Object permission) | 如果用户已访问给定权限的提供的目标,则返回true,例如hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) | 如果用户已访问给定权限的提供的目标,则返回true,例如hasPermission(1, 'com.example.domain.Message', 'read') |
首先得写一个获取验证码方法,方法能够生成随机验证码并且把验证码存入session供验证的功能,这里就不写了
因为Spring Security未提供验证码的接口,所以需要我们自己写一个过滤器处理
VaildCodeFilter 验证码校验过滤器
/**
* Security 登录验证码验证
*
* @author Peng
*/
public class VaildCodeFilter extends OncePerRequestFilter {
private SecurityLoginFailHandler securityLoginFailHandler;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 登录路径必须为/api/formlogin才生效
if ("/api/formlogin".equals(request.getRequestURI())) {
HttpSession session = request.getSession();
try {
String code = request.getParameter(页面验证码字段);
String sessionCode = (String) session.getAttribute(验证码sessionId);
if (code == null || sessionCode == null) {
throw new VaildCodeException("验证码不存在");
}
if (!sessionCode.equals(code)) {
throw new VaildCodeException("验证码不匹配");
}
} catch (VaildCodeException e) {
securityLoginFailHandler.onAuthenticationFailure(request, response, e);
return;
}
session.removeAttribute(验证码sessionId);
}
filterChain.doFilter(request, response);
}
public void setSecurityLoginFailHandler(SecurityLoginFailHandler securityLoginFailHandler) {
this.securityLoginFailHandler = securityLoginFailHandler;
}
}
最后在WebSecurityConfig configure中配置
@Override
protected void configure(HttpSecurity http) throws Exception {
// 在UsernamePasswordAuthenticationFilter过滤器前添加自定义验证码过滤器
VaildCodeFilter vaildCodeFilter = new VaildCodeFilter();
vaildCodeFilter.setSecurityLoginFailHandler(securityLoginFailHandler);
http.addFilterBefore(vaildCodeFilter, UsernamePasswordAuthenticationFilter.class);
http.formLogin().loginPage("/api/login")
.successHandler(securityLoginSuccessHandler)
.failureHandler(securityLoginFailHandler)
.loginProcessingUrl("/api/formlogin");
http.authorizeRequests()
.antMatchers("/api/login", "/static/*", "/api/codeImg").permitAll()
.antMatchers("/api/me").hasRole("ADMIN")
.anyRequest()
.authenticated();
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/api/login")
.invalidateHttpSession(true)
.addLogoutHandler(securityLoginoutHandler)
.logoutSuccessHandler(securityLoginoutSuccessHandler);
// 指定退出登录后需要删除的cookie名称,多个cookie之间以逗号分隔。
// .deleteCookies(cookieNamesToClear)
http.csrf().disable();
}