Springboot + Spring Security 实现前后端分离登录认证及权限控制
文章目录
一、环境准备
-
idea 2020.1
-
jdk 1.8
-
springBoot 2.26
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.2.6.RELEASE</version> </dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.60</version> </dependency>
二、创建Spring Security核心配置类:WebSecurityConfig。在这里配置权限控制,异常处理。
/**
* @author tyeerth
* @date 2020/5/1 - 15:39
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置认证方式等
super.configure(auth);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//http相关的配置,包括登入登出、异常处理、会话管理等
super.configure(http);
}
}
三、创建用户登入认证类UserDetailsServiceImpl。
1、创建自定义异常类。
/**
* 登录失败后抛出的异常
* @author Lenovo
*
*/
//public class LoginFailedException extends RuntimeException {
public class LoginFailedException extends RuntimeException {
public LoginFailedException() {
}
public LoginFailedException(String message) {
super(message);
}
public LoginFailedException(String message, Throwable cause) {
super(message, cause);
}
public LoginFailedException(Throwable cause) {
super(cause);
}
public LoginFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
2、创建UserDetailsServiceImpl类,并实现UserDetailsService接口。
(1)重载父类的loadUserByUsername方法。
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}
(2)用户登入逻辑
/**
* @author tyeerth
* @date 2020/5/1 - 15:50
*/
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private StudentService studentService;
private static final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
/**
总目标:根据表单提交的用户名查询User对象,并装配角色、权限等信息
*/
@Override
public UserDetails loadUserByUsername(
// 表单提交的用户名
String username
) throws UsernameNotFoundException {
HttpSession session = ContextHolderUtils.getRequest().getSession();
String password = ContextHolderUtils.getRequest().getParameter("password");
Student studentByCardNumber = studentService.getStudentByCardNumber(username);
session.setAttribute(LoginConsts.STUDENT,studentByCardNumber);
if (username == null || "".equals(username)) {
throw new LoginFailedException("用户名不能为空");
}
if (studentByCardNumber ==null){
throw new LoginFailedException("当前用户不存在");
}
if (!MD5Utils.md5(password).equals(studentByCardNumber.getPassword())){
throw new LoginFailedException("用户名和密码不匹配");
}
// 2.给Admin设置角色权限信息
List<GrantedAuthority> authorities = new ArrayList<>();
//添加用户权限
authorities.add(new SimpleGrantedAuthority(studentByCardNumber.getRole()));
// authorities.add(new SimpleGrantedAuthority("UPDATE"));
logger.info("从session域中获取到的密码"+password);
return new SecurityStudent(studentByCardNumber, authorities);
}
}
四、密码的盐值加密方式。
1、定义md5的加密方式
@Component
public class PasswordEncoderService implements PasswordEncoder {
/**
* encode()方法对明文进行加密。
* @param rawPassword
* @return
*/
@Override
public String encode(CharSequence rawPassword) {
return MD5Utils.md5(rawPassword.toString());
}
/**
* matches()方法对明文加密后和密文进行比较
* @param rawPassword
* @param encodedPassword
* @return
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String result = MD5Utils.md5(rawPassword.toString());
return Objects.equals(result, encodedPassword);
}
}
2、使用自定义的盐值加密方式
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
五、权限过滤配置
1、设置指定访问首页
@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/index.jsp") //使用 ANT 风格设置要授权的 URL 地址
.permitAll() //允许上面使用 ANT 风格设置的全部请求
.anyRequest() //其他未设置的全部请求
.authenticated() //需要认证
}
2、在前后端分离中,未登入的情况返回对应的json数据。
结果示例:
更新配置类
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)//登录成功处理逻辑
. failureHandler(authenticationFailureHandler)//登录失败处理逻辑
.and()
.exceptionHandling() // 指定异常处理器
.authenticationEntryPoint(authenticationEntryPoint)//匿名用户访问无权限资源时的异常处理
.and()
.logout()
.permitAll()//允许所有用户
.logoutSuccessHandler(logoutSuccessHandler)//登出成功处理逻辑
.deleteCookies("JSESSIONID")//登出之后删除cookie// 开启退出功能
(1)匿名访问时返回的json数据
/**
* 匿名访问时返回的json数据
* @author tyeerth
* @date 2020/5/2 - 13:26
*/
@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg","用户未登入");
jsonObject.put("status","401");
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(jsonObject.toJSONString());
}
}
3、登入成功逻辑处理
(1)登入成功逻辑处理
/**
* 登入成功逻辑处理
* @author tyeerth
* @date 2020/5/2 - 13:42
*/
@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//返回json数据
JSONObject jsonObject = new JSONObject();
jsonObject.put("status",200);
jsonObject.put("msg","登入成功");
//处理编码方式,防止中文乱码的情况
httpServletResponse.setContentType("text/json;charset=utf-8");
//塞到HttpServletResponse中返回给前台
httpServletResponse.getWriter().write(jsonObject.toJSONString());
}
}
4、失败处理逻辑
/**
* 登入失败的处理
*
* @author tyeerth
* @date 2020/5/2 - 13:47
*/
@Component
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//返回json数据
JSONObject jsonObject = new JSONObject() ;
if (e instanceof AccountExpiredException) {
//账号过期
jsonObject.put("msg","账号过期");
} else if (e instanceof BadCredentialsException) {
//密码错误
jsonObject.put("msg","密码错误");
} else if (e instanceof CredentialsExpiredException) {
//密码过期
jsonObject.put("msg","密码过期");
} else if (e instanceof DisabledException) {
//账号不可用
jsonObject.put("msg","账号不可用");
} else if (e instanceof LockedException) {
//账号锁定
jsonObject.put("msg","账号锁定");
} else if (e instanceof InternalAuthenticationServiceException) {
//用户不存在
jsonObject.put("msg",e.getMessage());
jsonObject.put("status","402");
} else {
//其他错误
jsonObject.put("msg","其他错误");
}
//处理编码方式,防止中文乱码的情况
httpServletResponse.setContentType("text/json;charset=utf-8");
//塞到HttpServletResponse中返回给前台
httpServletResponse.getWriter().write(jsonObject.toJSONString());
}
}
5、退出登入逻辑
/**
* 成功退出登入的逻辑
* @author tyeerth
* @date 2020/5/2 - 13:50
*/
@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
JSONObject jsonObject =new JSONObject();
jsonObject.put("msg","成功退出登入");
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(jsonObject.toJSONString());
}
}
六、会话管理(登录过时、限制单用户或多用户登录等)
1、限制用户登入
and().sessionManagement().
maximumSessions(1);//只能一个人登入
2、处理账号被挤下线处理逻辑
(1)会话管理逻辑
/**
* 会话过期策略
* @author tyeerth
* @date 2020/5/2 - 14:29
*/
@Component
public class CustomizeSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException, ServletException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg","当前会话已过期,请重新登入");
HttpServletResponse httpServletResponse = sessionInformationExpiredEvent.getResponse();
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(jsonObject.toJSONString());
}
}
(2)配置
.and().sessionManagement().//限制同一账号只能一个用户使用
maximumSessions(1)
.expiredSessionStrategy(sessionInformationExpiredStrategy)//会话信息过期策略会话信息过期策略(账号被挤下线)