springsecurity使用接口UserService中的loadUserByUsername(String username);查询数据库中的用户信息,所以我们需要自定义一个UserServiceImpl来实现UserService,重写loadUserByUsername(String username)方法。springsecurity需要用UserDetails来实现认证和授权。所以我写了一个类来实现UserDetails这个接口,并且将用户实体作为里边的一个属性。
UserDetailsService实现
package com.guizhou.springsecurity;
import com.guizhou.springsecurity.entity.UserDetail;
import com.guizhou.springsecurity.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @Description:
* @auther: libiao
* @Email: [email protected]
* @Date: 2020-6-17 10:35
* @Copyright: (c) 2019-2022 XXXX公司
*/
@Service
@Slf4j
public class UserSecurityServiceImpl implements UserDetailsService {
@Resource
UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("login:{}", username);
UserDetail user = userService.loadUserByUsername(username);
if(user == null){
throw new UsernameNotFoundException("未找到");
}
return user;
}
}
UserDetails的实现
package com.guizhou.springsecurity.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author xxxx
*/
@Data
public class UserDetail implements UserDetails {
// 这个才是用户实体
protected SysUser sysUser;
/**
* @Description: 装配角色
* @author libiao
* @Date 2020-6-17 17:45
* @Param
* @return
**/
@Override
public Collection extends GrantedAuthority> getAuthorities() {
List grantedAuthorities = new ArrayList<>();
if (sysUser == null) {
return grantedAuthorities;
}
List sysRoles = sysUser.getSysRoles();
if (!CollectionUtils.isEmpty(sysRoles)) {
for (SysRole sysRole : sysRoles) {
String code = sysRole.getCode();
grantedAuthorities.add(new SimpleGrantedAuthority(code));
}
}
return grantedAuthorities;
}
@Override
public String getPassword() {
return sysUser == null ? "" : sysUser.getPassword();
}
@Override
public String getUsername() {
return sysUser == null ? "" : sysUser.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
还自定义了认证成功的Handler和失败的Handler
package com.guizhou.springsecurity.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description:
* @auther: libiao
* @Email: [email protected]
* @Date: 2020-6-17 15:41
* @Copyright: (c) 2019-2022 XXXX公司
*/
@Component
@Slf4j
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
log.info("登陆成功:" + authentication);
super.onAuthenticationSuccess(request, response, authentication);
}
}
package com.guizhou.springsecurity.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description:
* @auther: libiao
* @Email: [email protected]
* @Date: 2020-6-17 15:44
* @Copyright: (c) 2019-2022 XXXX公司
*/
@Component
@Slf4j
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
/*@Autowired
public MyAuthenticationFailureHandler() {
super("/");
}*/
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("登陆失败!" + exception);
super.saveException(request, exception);
if (exception instanceof UsernameNotFoundException) {
}
// 重定向地址,上边注释的代码也可以实现(源码中体现)
redirectStrategy.sendRedirect(request,response,"/toLoginPage");
//super.onAuthenticationFailure(request, response, exception);
}
}
springsecurityConfig中的代码
package com.guizhou.springsecurity.config;
import com.guizhou.springsecurity.UserSecurityServiceImpl;
import com.guizhou.springsecurity.utils.PasswordEncoderImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.DigestUtils;
import javax.annotation.Resource;
/**
* @Description:
* @auther: libiao
* @Email: [email protected]
* @Date: 2020-6-16 16:26
* @Copyright: (c) 2019-2022 XXXX公司
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
UserSecurityServiceImpl userSecurityService;
@Resource
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Resource
MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 这个是logout方法报404,关闭csrf跨站请求伪造。如果使用Post请求logout方法,就可以
http.csrf().disable();
http.authorizeRequests().antMatchers("/", "/index").permitAll()
.antMatchers("/toVip/1").hasRole("vip1")
.antMatchers("/toVip/2").hasRole("vip2")
.antMatchers("/toVip/3").hasRole("vip3")
.and().formLogin().loginPage("/toLoginPage").loginProcessingUrl("/login")
// 失败和成功的handler
.successForwardUrl("/").successHandler(myAuthenticationSuccessHandler).
failureHandler(myAuthenticationFailureHandler)
;
http.logout().logoutSuccessUrl("/");
// 开启记住我,和记住的时间
http.rememberMe().rememberMeParameter("rememberMe").tokenValiditySeconds(60)
.and();
}
// 加密,也是自定义实现
@Bean
public PasswordEncoder passwordEncoder(){
return new PasswordEncoderImpl();
}
// 因为springsecurity默认是隐藏了一些错误提示信息,
//AbstractUserDetailsAuthenticationProvider中的protected boolean hideUserNotFoundExceptions = true;,所以我们需要手动将这个属性设置为false
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 必须要设置加密方式
provider.setPasswordEncoder(passwordEncoder());
// 必须要设置 userSecurityService
provider.setUserDetailsService(userSecurityService);
// 这个就是将错误信息放开
provider.setHideUserNotFoundExceptions(false);
return provider;
}
}
将错误信息放开后,我们就可以根据实际情况提示更友善的信息,例如
// 查询的时候,用户为空
SysUser sysUser = sysUserMapper.loadUserByUsername(username);
if (sysUser == null) {
throw new UsernameNotFoundException(username + "未找到!");
}
// 或者用户被锁定
throw new LockedException("用户被锁定了");
具体的可以看AuthenticationException的实现类
因为我在自定义的失败的handler中设置过错误信息,所以可以直接在session中取
super.saveException(request, exception);
登陆页获取错误信息
遇到的坑:登陆页面中的用户名,密码的name一定要和config中的配置一样,用默认的就可以了(今天写demo的时候,手残把config中的名字改了,没有注意到。UserDetailsService中就一直拿不到前台传入的用户名)
启动报错:java.io.EOFException: null(我是百度的,博文地址)
可能原因是由于tomcat上次非正常关闭时有一些活动session被持久化(表现为一些临时文件),在重启时,tomcat尝试去恢复这些session的持久化数据但又读取失败造成的。EOFException表示输入过程中意外地到达文件尾或流尾的信号,导致从session中获取数据失败。此异常不影响系统的使用。
解决方法:
1.外部tomcat:
找到:tomcat/work/Catalina/localhost/项目名/SESSIONS.ser文件,将它删除
2.springboot内置tomcat
springboot内置tomcat路径不太好找,但是在报出这个异常时,控制台也会打印发生异常的文件路径,如下图
这一行的完整信息为:
Unable to delete [C:\Users\ADMINI~1\AppData\Local\Temp\AEEC93AEB69DEF3532F47F7AED7494FA49E5EEF3\servlet-sessions\SESSIONS.ser] after reading the persisted sessions. The continued presence of this file may cause future attempts to persist sessions to fail.