我就说其中最主要的几个实现类吧,其实是不止这几个接口的。
首先去实现UserDetailsService 接口,因为UserDetails里面的取数据默认是在内存中取数据,所有我们要重写其中的UserDetails loadUserByUsername(String username),让他在数据库里去取数据
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper userMappe;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userMappe.findByUsername(username);
if(null == user){
throw new UsernameNotFoundException("用户名或密码错误");
}
if(user.isAdmin()){
//管理员需要查询角色信息
user.setRoles(userMappe.findRoles(null));
user.setPermissions(userMappe.findPermissions(null));
//获取父级菜单
List<SysMenu> menus = userMappe.findMenus(null);
//获取子级菜单
menus.forEach(item -> item.setChildren(userMappe.findChildrenMenus(item.getId(),null)));
user.setMenus(menus);
}else{
//非管理员需要查询相关角色信息
user.setRoles(userMappe.findRoles(user.getId()));
user.setPermissions(userMappe.findPermissions(user.getId()));
//获取父级菜单
List<SysMenu> menus = userMappe.findMenus(user.getId());
//获取子级菜单
menus.forEach(item -> item.setChildren(userMappe.findChildrenMenus(item.getId(),user.getId())));
user.setMenus(menus);
}
return user;
}
}
然后最为关键的SecurityConfig的配置,要配置很多东西,继承WebSecurityConfigurerAdapter类,然后重写里面的方法,我们边看代码边解释各个的作用
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired //这里放进来,是要和passwordEncoder(如何编码译码)注入到AuthenticationManagerBuilder中
private UserDetailServiceImpl userDetailService;
@Autowired//重写的一个类 用于没有权限访问时返回结果,后文会提到该类中具体怎么样重写
private JwtAccessDeniedHandler accessDeniedHandler;
@Autowired重写的一个类 用于当用户未登录和token过期的情况下访问资源
private JwtAuthenticationEntryPoint authenticationEntryPoint;
@Autowired 在接口访问前进行过滤 一般是检查头部是否有token
private JwtAuthenticationFilter authenticationFilter;
//一般用于配置白名单
//白名单:可以没有权限也可以访问的资源
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.mvcMatchers(SecurityContents.WHITE_LIST);//会有一个SecurityContents来返回白名单的内容
}
//security的核心配置
@Override
protected void configure(HttpSecurity http) throws Exception {
//1.使用jwt,首先关闭跨域攻击
http.csrf().disable();
//2.关闭session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//3.请求都需要进行认证之后才能访问,除白名单以外
http.authorizeRequests().anyRequest().authenticated();
//关闭缓存
http.headers().cacheControl();
//5.token过滤器,添加我们自己重写的filter去校验token (注意这里注入的过滤器)
http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
//6.没有登录,没有权限访问资源自定义返回结果(这一步注入的没有权限和过时的两个类),只有注入后springsecurity才会使用我们重写的方法
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);
}
//自定义登录逻辑配置
//也就是配置到security进行密码配置
//这一步比较关键,我们要重写AuthenticationManagerBuilder对密码的编译方式
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());//把我们的重写的userDetailService(如何取数据)和.passwordEncoder(如何编码译码)注入到AuthenticationManagerBuilder中,这样springsecurity才会按我们的方式进行编译
}
@Bean //返回我们想要的密码编译方式,在上一个方法中要用到
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
上文提到的白名单
public class SecurityContents {
public static final String[] WHITE_LIST = {
//后端登录接口
"/user/login",
//swagger相关
"/swagger-ui.html",
"/webjars/**",
"/swagger-resources/**",
"/v2/**",
"/configuration/ui",
"/configuration/security",
"/doc.html"
};
}
然后就是上文中注入的三个类
JwtAccessDeniedHandler
//没有权限访问时返回结果 不同的事情实现不同的接口 实现的接口和重写的方法是固定的
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setStatus(403); //设置这种情况的返回码
response.setCharacterEncoding("UTF-8");//设置字符序列
response.setContentType("application/json");//返回格式
PrintWriter writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(Result.fail("权限不足,请联系管理员")));
writer.flush();
writer.close();
}
}
JwtAuthenticationEntryPoint 与上面那个类型配置东西相似,就是返回的message不同,重写的方法不同
//当用户未登录和token过期的情况下访问资源
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setStatus(401);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(Result.fail("你尚未登录,请登陆后操作")));
writer.flush();
writer.close();
}
}
JwtAuthenticationFilter我们自己添加的一个过滤器,记得拦截后记得放行,不要害怕放行,反正springsecurity自己还要继续拦截
// token认证
// 在接口访问前进行过滤
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private TokenUtil tokenUtil; //自己写的一个token工具类主要就是生成token 、获取token、 通过token获得用户信息呀等等..
@Autowired
private UserDetailServiceImpl userDetailsService;
@Value("${jwt.tokenHeader}") //获取配置文件中设置好的头部信息
private String tokenHeader;
@Value("${jwt.tokenHead}")//获取配置文件中设置好的头部信息
private String tokenHead;
//请求前获取请求头信息(反正记住重写这个方法就可以在请求前做一些事情)
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
//1.获取token(从前端发来的请求中去获取head)
String header = request.getHeader(tokenHeader);
System.out.println("1111"+header);
//2.判断token是否存在
if(null != header && header.startsWith(tokenHead)){
//拿到token主体
String token = header.substring(tokenHead.length());
//根据token获取用户名
String username = tokenUtil.getUserNameByToken(token);
//token存在,但是没有登录信息
if(null !=username && null == SecurityContextHolder.getContext().getAuthentication()){
//没有登录信息,但我们以token为主,则帮他自动登录
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
System.out.println("重新登录");
//判断token是否有效 (该if判断 是判断token是否过期没,比较用户名是否对得上之前登录的用户名,如果都是正确的,则重新刷新一下信息和token时间之类的)
if(!tokenUtil.isExpiration(token) && username.equals(userDetails.getUsername())){
//刷新security中的用户信息 该处传入的是主体信息,现在userDetails在登录后就被我们注满信息了,然后null是身份之类的把(我也不太清楚...哈哈),然后就是传入该用户的权限,权限的注入也是比较特殊
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
//这几步不太清楚,就是刷新信息固定要重新注入的数据吧
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
//过滤器放行
chain.doFilter(request,response);
}
}
TokenUtil这就是上文提到的token工具类
//Token工具类,用于生成token 用户登录拿到token然后访问我们的系统资源
@Component
public class TokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
// 传入用户登录信息,生成token
public String generateToken(UserDetails details){
Map<String,Object> map = new HashMap<>(2);
map.put("username",details.getUsername());
map.put("created",new Date());
return this.generateJwt(map);
}
private String generateJwt(Map<String,Object> map){
return Jwts.builder()
.setClaims(map)
.signWith(SignatureAlgorithm.HS512,secret)
.setExpiration(new Date(System.currentTimeMillis()+ expiration * 1000))
.compact();
}
//根据token获取荷载信息
public Claims getTokenBody(String token){
try{
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
return null;
}
}
//根据token获取用户名
public String getUserNameByToken(String token){
return (String) this.getTokenBody(token).get("username");
}
//根据token判断当前时间内,该token是否过期
public boolean isExpiration(String token){
return this.getTokenBody(token).getExpiration().before(new Date());
}
//刷新token令牌
public String refreshToken(String token){
Claims claims = this.getTokenBody(token);
claims.setExpiration(new Date());
return this.generateJwt(claims);
}
}
SecurityUtil的工具类,就是从从springsecurity设置好的主体中取信息之类的方法,
public class SecurityUtil {
//从security主体信息中获取用户信息
public static SysUser getUser(){
SysUser user = (SysUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
user.setPassword(null);
return user;
}
//在security中获取用户名
public static String getUsername(){
return getUser().getUsername();
}
//获取id
public static Long getUserId(){
return getUser().getId();
}
}
差不多想在自己的项目中使用springsecurity就是要重写这些类吧