一、編寫UserDetailService類實現(xiàn)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;
/**
* 用來查詢數(shù)據(jù)庫用戶的類
*/
@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 查詢用戶權(quán)限
//4.返回用戶信息
return new LoginUser(user);
}
}
二、在controller對應(yīng)的service實現(xiàn)類里面
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)表服務(wù)實現(xiàn)類
*
* @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());
// 這個方法會調(diào)用UserDetailServiceImpl里面的方法去查詢數(shù)據(jù)庫,將結(jié)果返回給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
//關(guān)閉csrf
.csrf().disable()
//不通過Session獲取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 對于登錄接口 允許匿名訪問
.antMatchers("/login").anonymous()
// 除上面外的所有請求全部不需要認(rèn)證即可訪問
.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();
}
}