springsecurity和jwt的后端配置

该文详细介绍了如何使用SpringSecurity进行用户认证,通过实现UserDetailsService接口从数据库获取用户信息,并配置SecurityConfig,包括关闭CSRF、禁用session、自定义登录逻辑以及处理无权限和未登录的异常。此外,文章还展示了JWTAuthenticationFilter的实现,用于在请求前验证token并处理登录状态。
摘要由CSDN通过智能技术生成

我就说其中最主要的几个实现类吧,其实是不止这几个接口的。
首先去实现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就是要重写这些类吧
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值