Java

SpringSecurity整合JWT

2019-09-18  本文已影响0人  TZX_0710

SpringSecurity前面讲解的一些配置都是基于前后端都是一起的,那么当分开了的时候就会出现一系列问题,跨域之类的问题随之而出 所以出现了jwt帮助我们完成SpringSecurity前后端分离权限控制
jwt (json web Token)它是基于RFC 7519开放标准用于双方安全展示信息的一种方式。通俗说就是是用于服务端和客户端相互交换信息的一种凭证。如果用过aouth2的小伙伴本对这个应该不陌生Token。
在传统模式当中我们的认证流程是
用户登录->服务端生成session->cookie ->服务端写代session->查找用户->findOK

1.服务端需一定资源保存session信息,用户多时资源消耗较大
扩展性不好,当我们的服务端需要集群时,
2.因session保存在服务端,此时无法定位session,造成登录失效
3.跨域问题,当我们访问A网站时,此时不想再登录就能够访问关联网站B。(传统解决办法:写入持久层,A,B同时访问)
所以现在可以采用的解决办法,不需要服务端去保存session,
用户登录--->服务端生成凭证->携带凭证带客户端——>客户端保存凭证->每次客户端请求都携代凭证,客户端通过则验证成功,调用API获取信息。所以就有了jwt的诞生

jwt的组成部分

  1. header(头),保存算法,类型
  2. payload(负载),用户的信息,如id,用户名等等
  3. signature(签名),将生成的token编码(加密)
    他们之间用 "."号隔开。

新建springboot项目
引入pom

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

编写yml文件

server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url:  jdbc:mysql://localhost:3306/security_db?useSSL=false&serverTimezone=UTC

  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true

logging:
  level:
    org.springframework.*: debug

参考之前文章,编写实体和repository这里就不放代码了 直接放截图


image.png

因为是前后端分离的所有封装了一个专们响应前台数据的实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseDto<V> {

    
    private int code;

    private String msg;

    private V data;
}
//编写securityConfig


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

    //添加异常
    @Resource
    private TokenExceptionHandler tokenExceptionHandler;

    @Resource
    private AccessDeniedHandler accessDeniedHandler;

    @Resource
    private JwtTokenFilter jwtTokenFilter;
//获取Token的接口不必拦截
    @Override
    public void configure(WebSecurity web) throws Exception {

        web.ignoring().antMatchers( HttpMethod.GET,"/token" );
         }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //前后端分离所以不要考虑csrf可以禁用掉
        http.csrf().disable()
                //添加异常处理
                .exceptionHandling().authenticationEntryPoint( tokenExceptionHandler )

                .accessDeniedHandler( accessDeniedHandler )
                //
                .and().sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS )
                //拦截所有请求
                .and().authorizeRequests().anyRequest().authenticated();
        //定义filter addFilterAt  用于filter替换
        http.addFilterAt(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
        //super.configure( http );
    }
}

//对没有Token 的请求进行拦截  编写handler处理

//验证没有token异常
@Component
public class TokenExceptionHandler implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        // 直接返回 json错误
        ResponseDto <Object> result = new ResponseDto<>();
        //20,标识没有token
        result.setCode(20);
        result.setMsg("请求无效,没有有效token");

        ObjectMapper objectMapper = new ObjectMapper();

        response.getWriter().write(objectMapper.writeValueAsString(result));
    }
}

//访问被拒绝处理
@Component
public class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler {


    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        // 返回我们的自定义json
        ObjectMapper objectMapper = new ObjectMapper();
        ResponseDto <Object> result = new ResponseDto<>();
        //50,标识有token,但是该用户没有权限
        result.setCode(50);
        result.setMsg("请求无效,没有有效token");
        response.getWriter().write(objectMapper.writeValueAsString(result)); // 返回我们的自定义json
    }
}

//自定义Filter
@Component
public class JwtTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
         request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        String token = request.getHeader("token");

        //获取token,并且解析token,如果解析成功,则放入 SecurityContext
        if (token != null) {
            try {
                AuthUser authUser = JwtUtil.parseToken(token);
                //todo: 如果此处不放心解析出来的 authuser,可以再从数据库查一次,验证用户身份:

                //解析成功
                if (SecurityContextHolder.getContext().getAuthentication() == null) {
                    //我们依然使用原来filter中的token对象
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities());

                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                }
            } catch (Exception e) {
                logger.info("解析失败,可能是伪造的或者该token已经失效了(我们设置失效5分钟)。");
            }
        }

        filterChain.doFilter(request, response);
    }
}

controller代码

@RestController
public class UserController {


    @Resource
    private UserRepository userRepository;
    @Resource
    private RoleRepository roleRepository;

    @GetMapping("/token")
    public ResponseDto login(String username, String password) {
        User user = userRepository.findByUsername(username);

        if (user == null || !user.getPassword().equals(password)) {
            ResponseDto <Object> result = new ResponseDto <>();
            result.setCode(10);
            result.setMsg("用户名或密码错误");
            return result;
        }

        ResponseDto <Object> success = new ResponseDto <>();
        //用户名密码正确,生成token给客户端
        success.setCode(0);
        List <Role> roles = Collections.singletonList(roleRepository.findById(user.getId()).get());
        success.setData( JwtUtil.generateToken(username, roles));

        return success;
    }
}

@RestController
@RequestMapping
public class PermissionController {
    @GetMapping("/permission")
    public ResponseDto loginTest(@AuthenticationPrincipal AuthUser authUser) {
        ResponseDto<String> resultVO = new ResponseDto<>();
        resultVO.setCode(0);

        resultVO.setData("你成功访问了该api,这代表你已经登录,你是: " + authUser);
        return resultVO;
    }

    @GetMapping("/loginUser")
    @PreAuthorize("hasRole('user')")
    public ResponseDto loginTest() {
        ResponseDto<String> resultVO = new ResponseDto<>();
        resultVO.setCode(0);

        resultVO.setData("你成功访问了需要有 user 角色的api。");
        return resultVO;
    }
}

采用postman进行测试 直接放图


image.png image.png

带入token在header里面去访问 不需要角色的接口


image.png
image.png

新建一个没有该角色的用户去访问


image.png

总结:jwt+security整合流程

  1. 引入jwt security的pom文件
  2. 创建实体 实现userDetail 和security交互的实体类
  3. 自定义需要处理的异常 访问被拒绝的异常等、看个人所需
    4.编写filter 自定义filter 去验证token是否通过 ,以及保证失效token不会进入接口
  4. 编写securityconfig 配置websecurity 让获取token接口绕过security 编写httpsecurity设置设置对web端所有的都进行拦截 都需要经过验证才行,添加异常处理
    添加对filter替换。替换掉UsernamePasswordAuthenticationFilter 该filter默认情况响应的是/login
上一篇下一篇

猜你喜欢

热点阅读