SpringSecurity设置JWT token令牌,权限控制

SpringSecurity设置JWT token令牌,权限控制

JWT token工具类编写

mave

		<!--JWT-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

在springboot的 application.yaml 配置文件中

# JWT
jwt:
  # JWT存储的请求头
  tokenHeader: Authorization
  # JWT 解密加密使用的密钥
  secret: yeb-secret
  # JWT的超期限时间(30*60*24)
  expiration: 604800
  # JWT 负载中拿到开头
  tokenHead: Bearer

编写配置工具类:JwtTokenUtil


/**
 * JwtTokenUtil工具类
 */
@Component
public class JwtTokenUtil {
    // 用户名的key
    private static final String CLAIM_KEY_USERNAME = "sub";
    // JWT 的创建时间 
    private static final String CLAIM_KEY_CREATED = "created";
    @Value("${jwt.secret}")//从yaml文件中取值:yeb-secret
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 根据用户信息生成token
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());

        return generateToken(claims);
    }

    /**
     * 根据荷载生成JWT Token
     */
    public String generateToken(Map<String, Object> claims) {
        System.out.println("secret" + secret);
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate()) // 失效时间
                .signWith(SignatureAlgorithm.HS512, secret) // 签名
                .compact();
    }
	/*
	* 生成失效时间
	*/
    public Date generateExpirationDate() {
        // 失效时间是就当前系统时间加 有效时间
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 从token中获取登录的用户名
     */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getUserClaimFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            username = null;
        }
        return username;

    }

    /**
     * 从token中获取荷载
     */
    private Claims getUserClaimFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return claims;
    }

    /**
     * 验证token是否有效  (token有效 + 用户一致)
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        String username = getUserNameFromToken(token);// 源于token

        // 然后在token有效的前提下对比用户名是否一致
        return !isTokenExpired(token) && username.equals(userDetails.getUsername());
    }

    /**
     * 判断token是否以及失效
     */
    public boolean isTokenExpired(String token) {
        Date expireDate = getExpiredDateFromToken(token);
        return expireDate.before(new Date());
    }
    /**
     * 获取token过期时间
     */
    public Date getExpiredDateFromToken(String token) {
        Claims claims = getUserClaimFromToken(token);
        return claims.getExpiration();
    }
    /**
     * 判断token是否可以被刷新
     */
    public boolean canRefresh(String token) {
        return !isTokenExpired(token);
    }

    /**
     * 刷新token
     */
    public String refreshToken(String token) {
        Claims claims = getUserClaimFromToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }
}

公共返回类编写:RespBean

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
    private long code;
    private String message;
    private Object result;

    /**
     * 成功返回 (提示)
     * @param message
     * @return
     */
    public static RespBean success(String message) {
        return new RespBean(200, message, null);
    }


    /**
     * 成功返回 (结果)
     * @param message
     * @param result
     * @return
     */
    public static RespBean success(String message, Object result) {
        return new RespBean(200, message, result);
    }
    
    /**
     * 失败返回 (提示)
     * @param message
     * @return
     */
    public static RespBean error(String message) {
        return new RespBean(500, message, null);
    }

    public static RespBean warning(String message) {
        return new RespBean(500, message, null);
    }
}

登录之后返回 token

在登录用户的实体类 Admin 上实现 UserDetails

重写方法:


    @Override
    @JsonDeserialize(using = CustomAuthorityDeserializer.class)
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }

controller类:AdminController


@RestController
@CrossOrigin
public class LoginController {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private IAdminService adminService;

    @Autowired
    private PasswordEncoder passwordEncoder;   //密码使用对称密码加密 判断密码是否一致 需要注入在 SecurityConfig 配置类中

    @Value("${jwt.tokenHead}")    // 根据value注解拿到yml中设置的token头
    private String tokenHead;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
   
	@PostMapping("/login")
    public RespBean login(@RequestBody Map loginForm, HttpServletRequest request) throws IOException {

        Map<String,Object> result = new HashMap<>();
        System.out.println(loginForm);
        String username = loginForm.get("username").toString();

        //是否可以用
        if (!adminService.getAdminByUserName(username).isEnabled()){
            System.out.println();
            return RespBean.warning("账号已经禁用,请联系管理员! ");
        }

        String password = loginForm.get("password").toString();
        String code = loginForm.get("code").toString();
        Object code1 = request.getSession().getAttribute("captcha");

        // 登录
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);  // (查库)

        if (!StringUtils.isEmpty(code1) && code1.equals(code)) {
            if (userDetails!=null && !passwordEncoder.matches(password, userDetails.getPassword())) {
                System.out.println("200");

                // 更新security登录用户对象
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new
                        UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());//第二个参数为口令(密码)不用传
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                //生成token
                String token = jwtTokenUtil.generateToken(userDetails);

                result.put("token", token);
                result.put("tokenHead", tokenHead);
                return RespBean.success("登录成功", result);
            } else {
                System.out.println("401");
                return RespBean.warning("用户名或密码错误");
            }
        } else {
            System.out.println("403");
            return RespBean.warning("验证码错误,请重新输入");
        }

    }
}

在网页中:每次请求头中带上
在这里插入片描述

Security 授权登录过滤器

spring的完全框架

maven

 		<!--security 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.5.5</version>
        </dependency>

SecurityConfig


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    IAdminService adminService;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private CustomUrlDecisionManger customUrlDecisionManger;
    @Autowired
    private CustomFilter customFilter;
    @Autowired
    private RestAuthorizationEnrtyPoint restAuthorizationEnrtyPoint;

    //放行一些路径,且不走拦截链
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
            	"/login",
                "/logout",
                "/css/**",
                "/js/**",
                "/index.html",
                "/favicon.ico",
                "/doc.html",
                "/webjars/**",
                "/swagger-resources/**",
                "/v2/api-docs/**",
                "/captcha",
                "/export/**",
                "/import/**",
                "/ws/**"
        );
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭csrf防火墙,使用jwt
        http.csrf().disable()
                // 基于token, 不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 允许登录访问
//                .antMatchers("/login", "/logout").permitAll()
                // 除了上面的请求,其他的都要认证
                .anyRequest()
                .authenticated()
                // 动态权限配置
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(customUrlDecisionManger);
                        o.setSecurityMetadataSource(customFilter);
                        return o;
                    }
                })
                .and()
                // 禁用缓存
                .headers()
                .cacheControl();

//        // 记住我,
//        http.rememberMe()
//                .tokenRepository(persistentTokenRepository())
//                .rememberMeParameter("rememberMe")
//    //            .tokenValiditySeconds()    修改默认记住我的时间,默认为两周
//                .userDetailsService(userDetailsService());


        // 添加  JWT 登录授权过滤器
        http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        // 添加自定义未授权和未登录结果的返回
        http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandel())
                .authenticationEntryPoint(restAuthorizationEnrtyPoint);
    }

    @Override
    @Bean
    public UserDetailsService userDetailsService() {  // 重写的了返回的参数(Admin)
        return username -> {
            Admin admin = adminService.getAdminByUserName(username);
            if (admin != null) {
                // 设置一下权限
                admin.setRoles(adminService.getRoles(admin.getId()));
                return admin;
            }

//            String password = passwordEncoder().encode("123");
//            设置权限

            throw new UsernameNotFoundException("用户名或密码不正确");
        };
    }


	//passwordEncoder使用BCryptPasswordEncoder加密器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        //设置数据源
        jdbcTokenRepository.setDataSource(dataSource);
        //自动建表,第一次启动时开启,之后需要注释掉
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Bean
    public JwtAuthencationTokenFilter jwtAuthencationTokenFilter() {
        return new JwtAuthencationTokenFilter();
    }

    @Bean
    public RestfulAccessDeniedHandel restfulAccessDeniedHandel() {
        return new RestfulAccessDeniedHandel();
    }
}

JWT 登录授权过滤器

JwtAuthencationTokenFilter


/**
 * jwt 授权登录器
 */
public class JwtAuthencationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    // 前置拦截
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader(tokenHeader); // 获取token值
        // 存在token  (获取的token不为空,且token前面携带的表示是我们设置的标识)
        if (authHeader != null && authHeader.startsWith(tokenHead)){
            String authToken = authHeader.substring(tokenHead.length());// 截取下除了头部标识以后的token
            String username = jwtTokenUtil.getUserNameFromToken(authToken); // 通过token就可以拿到用户名(解析荷载部分)
            // 看看username存在,但是未登录(就是检测是不是设置在security全局中)
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null){
                //登录
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                // 验证token是否有效,重新设置用户对象
                if (jwtTokenUtil.validateToken(authToken,userDetails)){
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                    // 重新设置到用户对象中
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }
        //放行
        filterChain.doFilter(request,response);
    }
}

添加自定义未授权和未登录结果的返回


/**
 *  当未登录或token失效的时候访问接口时,返回的自定义结果
 */
@Controller
public class RestAuthorizationEnrtyPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter printWriter = response.getWriter();

        RespBean respBean = RespBean.error("登录失效,请重新登录");
        respBean.setCode(401);
        printWriter.write(new ObjectMapper().writeValueAsString(respBean));
        printWriter.flush();  // 强推到浏览器
        printWriter.close();

    }
}

/**
 * 当访问接口没有权限的时候,自定义返回结果
 */
public class RestfulAccessDeniedHandel implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter printWriter = response.getWriter();

        RespBean respBean = RespBean.error("权限不足,请联系管理员");
        respBean.setCode(403);
        printWriter.write(new ObjectMapper().writeValueAsString(respBean));
        printWriter.flush();  // 强推到浏览器
        printWriter.close();
    }
}

权限控制

根据请求url,分析请求所需角色

/**
 * 权限控制
 * 根据请求url,分析请求所需角色
 */
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private IMenuService menuService;

    // 匹配url的实例
    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        // 获取请求的Url
        String requestUrl = ((FilterInvocation) o).getRequestUrl();

        // 获取菜单信息
        List<Menu> menuList = menuService.getMenusWithRoles();
        for (Menu menu : menuList) {
            // 逐个与url进行匹配
            if (antPathMatcher.match(menu.getUrl(),requestUrl)) { //第一个是匹配规则,位置不要颠倒 **
                // 如果匹配成功(把这个路径对应的角色封装到一个数组)
                String[] strArray = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                return SecurityConfig.createList(strArray);
            }
        }
        // 如果匹配不上,就默认返回登录权限的路径
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}


判断用户角色

/**
 *  权限控制
 *  判断用户角色
 */
@Component
public class CustomUrlDecisionManger implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : collection) {
            // 当前url所需要的角色
            String needRole = configAttribute.getAttribute();
            // 判断角色是否登录即可访问的角色,此角色在CustomFilter中设置
            if("ROLE_LOGIN".equals(needRole)) {
                // 判断是否登录了
                if(authentication instanceof AnonymousAuthenticationToken) {
                    throw new AccessDeniedException("尚未登录,请登录");
                }else {
                    return;
                }
            }
            // 判断角色是否为url所需要的角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,请联系管理!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return false;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

crabin_lpb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值