Springboot+Spring-Security+JWT实现restful Api的权限管理以及token管理

前言
其实挺早就想写一篇关于jwt的博文去好好总结一下之前踩过的坑了,但是事情有点太多了,一直没抽出时间来写,刚好现在有点时间可以好好静下来写一遍(可能)有点质量的博文吧,毕竟一直都是看别人的博文去学习,我也好好写一遍吧哈哈。
看完这篇文章之后你可以知道

如何使用springboot,springSecurity,jwt实现基于token的权限管理
统一处理无权限请求的结果

整理一下思路

创建一个新工程时,我们需要思考一下我们接下来需要的一些步骤,需要做什么,怎么做。

搭建springboot工程
导入springSecurity跟jwt的依赖
用户的实体类
dao层
service层(真正开发时再写,这里就直接调用dao层操作数据库)
实现UserDetailsService接口
实现UserDetails接口
验证用户登录信息的拦截器
验证用户权限的拦截器
springSecurity配置
认证的Controller以及测试的controller

直接上手

源码地址:https://gitee.com/zhu_can_admin/security.git
引入相关依赖

   <!-- spring security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
            <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--lombok依赖-->

jwt工具包

public class JwtTokenUtils {

    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";

    private static final String SECRET = "jwtsecretdemo";
    private static final String ISS = "echisan";

    // 角色的key
    private static final String ROLE_CLAIMS = "rol";

    // 过期时间是3600秒,既是1个小时
    private static final long EXPIRATION = 3600L;

    // 选择了记住我之后的过期时间为7天
    private static final long EXPIRATION_REMEMBER = 604800L;

    // 创建token
    public static String createToken(String username,String role, boolean isRememberMe) {
        long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
        HashMap<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, role);
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setClaims(map)
                .setIssuer(ISS)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .compact();
    }
    
   //验证token合法性
    public static Boolean verifToken(String token, String username) {
        boolean isExpiration = isExpiration(token);
        String name = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody().getSubject();
        return (name.equals(username) && !isExpiration);
    }

    // 从token中获取用户名
    public static String getUsername(String token){
        return getTokenBody(token).getSubject();
    }

    // 获取用户角色
    public static String getUserRole(String token){
        return (String) getTokenBody(token).get(ROLE_CLAIMS);
    }

    // 是否已过期
    public static boolean isExpiration(String token) {
        try {
            return getTokenBody(token).getExpiration().before(new Date());
        } catch (ExpiredJwtException e) {
            return true;
        }
    }

    private static Claims getTokenBody(String token){
        return Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token)
                .getBody();
    }
}

user实体

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {
    @ApiModelProperty(value = "")
    private Long id;

    @ApiModelProperty(value = "")
    private String username;

    @ApiModelProperty(value = "")
    private String idcard;

    @ApiModelProperty(value = "")
    private String telephon;

    @ApiModelProperty(value = "")
    private String email;

    @ApiModelProperty(value = "")
    private String password;

    @ApiModelProperty(value = "")
    private String role;

    private List<GrantedAuthority> authorities;

    public void setAuthorities(List<GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    /**
     * 账户是否未过期,过期无法验证
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     *
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     *
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否可用  ,禁用的用户不能身份验证
     *
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

使用springSecurity需要实现UserDetailsService接口供权限框架调用查询数据库里面用户的信息

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userMapper.selectByName(s);
        //AuthorityUtils.commaSeparatedStringToAuthorityList将逗号分隔的字符集转成权限对象列表
        user.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole()));
        return user;
    }

}

配置拦截器
在spring-security中,这边需要实现两个过滤器。使用JWTAuthenticationFilter去进行用户账号的验证,使用JWTAuthorizationFilter去进行用户权限的验证。

JWTAuthenticationFilter继承于UsernamePasswordAuthenticationFilter
该拦截器用于获取用户登录的信息,只需创建一个token并调用authenticationManager.authenticate()让spring-security去进行验证就可以了,不用自己查数据库再对比密码了,这一步交给spring去操作。
这个操作有点像是shiro的subject.login(new UsernamePasswordToken()),验证的事情交给框架。
献上这一部分的代码。

JWTAuthorizationFilter继承于BasicAuthenticationFilter
该拦截器用于进行权限认证

// 用户账号的验证
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        //设置登陆url
        super.setFilterProcessesUrl("/login");
    }

    //登陆获取登陆信息
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //自动进行密码校验
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    }

    //验证成功后会进入这个方法
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        User user = (User) authResult.getPrincipal();
        String token = JwtTokenUtils.createToken(user.getUsername(), user.getRole(), true);
        // 返回创建成功的tokenuser
        // 但是这里创建的token只是单纯的token
        // 按照jwt的规定,最后请求的时候应该是 `Bearer token`
        response.setContentType("application/json;charset=utf-8");
        response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
        response.getWriter().write("{\"code\":\"200\",\"msg\":\"登录成功\",\"token\":\"" + token + "\"}");
    }


    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write("登陆失败: " + failed.getMessage());
    }
}
//进行权限认证
public class JWTAuthorizationFilter extends OncePerRequestFilter{
   @Autowired
    private UserDetailsService UserDetailsServiceImpl;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
        // 如果请求头中没有Authorization信息则直接放行了
        if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        //设置用户身份授权
        try {
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        } catch (TokenIsExpiredException e) {
           throw new RuntimeException("token解析错误");
        }
        chain.doFilter(request, response);
    }

    // 这里从token中获取用户信息并新建一个token
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException {
        String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
        User userDetails = (User) UserDetailsServiceImpl.loadUserByUsername(JwtTokenUtils.getUsername(token));
        String username = userDetails.getUsername();
        String role = userDetails.getRole();
        //验证token合法性
        if (JwtTokenUtils.verifToken(token, userDetails.getUsername())) {
            return new UsernamePasswordAuthenticationToken(username, null,
                    AuthorityUtils.commaSeparatedStringToAuthorityList(role));
        } else {
            throw new TokenIsExpiredException("token超时了或者携带的token不合法");
        }
    }
}

Handler 用来配置没有权限访问和token失效返回的数据

**
 *
 * @description:没有访问权限
 */
public class JWTAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.getWriter().write("{\"code\":\"403\",\"msg\":\"没有权限\"}");
    }
}
/**
 * @description:没有携带token或者token无效
 */
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write("{\"code\":\"405\",\"msg\":\"token失效或未携带token\"}");
    }
}

核心配置SecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//    @Autowired
//    private MyAuthencationFailureHandler failureHandler;
//    @Autowired
//    private MyAuthenticationSuccessHandler successHandler;
    @Autowired
    @Qualifier("userDetailsServiceImpl")
    private UserDetailsService userDetailsService;


    /**
     * 当前配置为form表单登录认证方式
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf
        http.cors().and().csrf().disable()
                .authorizeRequests()
                // 放行URL
                .antMatchers("/login").permitAll()
                .antMatchers("/swagger**/**").permitAll()
                .antMatchers("/webjars/**").permitAll()
                .antMatchers("/v2/**").permitAll()
                //admin权限才能访问
                //对应数据库role为ROLE_user
                .antMatchers("/index").hasAnyRole("user")
                //对应数据库role为admin
                .antMatchers("/system/user").hasAnyAuthority("admin")
                // 其他所有请求需要身份认证
                .anyRequest().authenticated()
                .and()
                //用户登录过滤器,校验用户名和密码
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                //用户权限拦截器
                    .addFilterBefore(jwtAuthorizationFilter, BasicAuthenticationFilter.class)
                // 不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //没有携带token或者token无效拦截器
                .exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
                //添加无权限时的处理拦截器
                .accessDeniedHandler(new JWTAccessDeniedHandler());
        //登出功能
        http.logout().logoutUrl("/logout");
    }


    //登录验证逻辑
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 使用自定义登录身份认证组件
        auth.userDetailsService(userDetailsService).passwordEncoder(getBCryptPasswordEncoder());
    }

    //跨域
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }

     //强散列哈谢加密实现
    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

contoller

@Controller
public class HomeController {

    // 登录成功首页
    @GetMapping("/index")
    public String index(){
        return "index";
    }

    //用户管理
    @GetMapping("/system/user")
    public String userList(){
        return "user";
    }

    //角色管理
    @GetMapping("/system/role")
    public String roleList(){
        return "role";
    }

    //菜单管理
    @GetMapping("/system/menu")
    public String menuList(){
        return "menu";
    }

    //订单管理
    @GetMapping("/order")
    public String orderList(){
        return "order";
    }




}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值