一、编写UserDetailService类实现UserDetailsService接口
package com.zh.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zh.domain.entity.LoginUser;
import com.zh.domain.entity.User;
import com.zh.mapper.UserMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.Objects;
/**
* 用来查询数据库用户的类
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.查询用户
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(queryWrapper);
//2.没查到,抛出异常
if (Objects.isNull(user)){
throw new RuntimeException("用户不存在");
}
//3.todo 查询用户权限
//4.返回用户信息
return new LoginUser(user);
}
}
二、在controller对应的service实现类里面
package com.zh.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zh.domain.ResponseResult;
import com.zh.domain.entity.LoginUser;
import com.zh.domain.entity.User;
import com.zh.domain.vo.BlogUserLoginVo;
import com.zh.domain.vo.UserInfoVo;
import com.zh.mapper.UserMapper;
import com.zh.service.BlogLoginService;
import com.zh.utils.BeanCopyUtils;
import com.zh.utils.JwtUtil;
import com.zh.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.util.Objects;
import static com.zh.constants.SystemConstants.LOGIN;
/**
* 用户表(User)表服务实现类
*
* @author makejava
* @since 2023-03-23 16:57:42
*/
@Service
public class BlogLoginServiceImpl extends ServiceImpl<UserMapper, User> implements BlogLoginService {
@Autowired
private RedisCache redisCache;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public ResponseResult login(User user) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
// 这个方法会调用UserDetailServiceImpl里面的方法去查询数据库,将结果返回给authenticate
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authenticate)){
throw new RuntimeException("密码错误");
}
//得到用户id,生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
//存入redis
redisCache.setCacheObject(LOGIN+userId,loginUser);
//将token和userInfo封装、返回
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
BlogUserLoginVo blogUserLoginVo = new BlogUserLoginVo(jwt, userInfoVo);
return ResponseResult.okResult(blogUserLoginVo);
}
}
三、编写JwtAuthenticationTokenFilter过滤器
这个过滤器要配置在UsernamePasswordAuthenticationFilter过滤器之前,在securityConfig配置文件中配置
package com.zh.filter;
import com.alibaba.fastjson.JSON;
import com.zh.domain.ResponseResult;
import com.zh.domain.entity.LoginUser;
import com.zh.domain.vo.BlogUserLoginVo;
import com.zh.enums.AppHttpCodeEnum;
import com.zh.utils.JwtUtil;
import com.zh.utils.RedisCache;
import com.zh.utils.WebUtils;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import static com.zh.constants.SystemConstants.LOGIN;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterchain) throws ServletException, IOException {
//1.拿到请求头token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)){
//说明该接口不需要登录 直接放行
filterchain.doFilter(request,response);
return;
}
//2.解析token,拿到用户id
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
//超时 token非法
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, JSON.toJSONString(result));
return;
}
//3.从redis获取用户信息
String userId = claims.getSubject();
LoginUser loginUser = redisCache.getCacheObject(LOGIN + userId);
if (Objects.isNull(loginUser)){
//说明登录过期,需要重新登录
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response,JSON.toJSONString(result));
return;
}
//4.存入SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterchain.doFilter(request,response);
}
}
四、securityConfig配置文件
package com.zh.config;
import com.zh.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
AccessDeniedHandler accessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/login").anonymous()
// 除上面外的所有请求全部不需要认证即可访问
.anyRequest().permitAll();
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
http.logout().disable();
//配置JwtAuthenticationTokenFilter过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);
//允许跨域
http.cors();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}