• Spring Boot+JWT+Spring Security实现授权认证保护Rest API


    通常情况下,把API直接暴露出去是风险很大的。那么一般来说,对API要划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户开放对应的API。目前,比较主流的方案有几种:

    1. 用户名和密码鉴权,使用Session保存用户鉴权结果。
    2. 使用OAuth进行鉴权(其实OAuth也是一种基于Token的鉴权,只是没有规定Token的生成方式)
    3. 自行采用Token进行鉴权

    这里主要讲一下JWT

    JWT定义:

    JWT是 Json Web Token 的缩写。它是基于 RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。

    JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。

    JWT的工作流程

    下面是一个JWT的工作流程图。模拟一下实际的流程是这样的(假设受保护的API在/protected中)

    1. 用户导航到登录页,输入用户名、密码,进行登录
    2. 服务器验证登录鉴权,如果改用户合法,根据用户的信息和服务器的规则生成JWT Token
    3. 服务器将该token以json形式返回(不一定要json形式,这里说的是一种常见的做法)
    4. 用户得到token,存在localStorage、cookie或其它数据存储形式中。
    5. 以后用户请求/protected中的API时,在请求的header中加入 Authorization: Bearer xxxx(token)。此处注意token之前有一个7字符长度的 Bearer
    6. 服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
    7. 用户取得结果

    添加maven依赖:

    <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.10.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    <dependencies>
        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>

        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter—web</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-thymeleaf</artifactId>
            </dependency>
    <dependencies>

     JWT生成的代码:

    static public String createAccessToken(Authentication auth) {
            AccountCredentials credentials = (AccountCredentials) auth.getDetails();
            String userName = auth.getName();
            String role = credentials.isAdmin() ? "ROLE_ADMIN" : "ROLE_USER";
            Calendar nowTime = Calendar.getInstance();
            nowTime.add(Calendar.MINUTE, TOKENEXPIRATIONTIME);
            String accessToken = Jwts.builder().claim("authorities", role).claim("username", userName)
                    .claim("userid", credentials.getUserId()).setSubject(userName).setIssuer(TOKENISSUER)
                    .setIssuedAt(new Date(System.currentTimeMillis())).setExpiration(nowTime.getTime())
                    .signWith(SignatureAlgorithm.HS512, SECRET).compact();
            return accessToken;
        }

    Security:

    入口过滤器

    @Configuration
    @EnableWebSecurity
    @Order(2)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
        @Value("${server.context-path}")
        String contextPath;
        
        @Override
        protected void configure(HttpSecurity http) throws Exception{
            http.headers().xssProtection().xssProtectionEnabled(true);
            http.csrf().disable().exceptionHandling().authenticationEntryPoint(myEntryPoint())
            .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and().authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS).permitAll().antMatchers("/").permitAll().antMatchers("/images/**")
            .permitAll().antMatchers("/*.html").permitAll().antMatchers("/hello/**").permitAll()
            .antMatchers("/admin/add").permitAll().antMatchers("/admin/encode").permitAll()
            .antMatchers("/admin/delete").permitAll().antMatchers("/category/list").permitAll()
            .antMatchers("/module/list").permitAll().antMatchers("/photo/list").permitAll()
            .antMatchers("/music/list").permitAll().antMatchers("/doc/list").permitAll().antMatchers("/vr/list")
            .permitAll().antMatchers("/video/list").permitAll().antMatchers("/category/list/privateOpen")
            .permitAll().antMatchers("/photo/list/privateOpen").permitAll().antMatchers("/video/list/privateOpen")
            .permitAll().antMatchers("/music/list/privateOpen").permitAll().antMatchers("/doc/list/privateOpen")
            .permitAll().antMatchers("/vr/list/privateOpen").permitAll().antMatchers("/health/**").permitAll()
            .antMatchers("/favicon.ico").permitAll().antMatchers("**/*.html").permitAll().antMatchers("**/*.css")
            .permitAll().antMatchers("**/*.js").permitAll().antMatchers("/", "/*swagger*/**", "/v2/api-docs")
            .permitAll()
            .antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security",
                    "/swagger-ui.html", "/webjars/**")
            .permitAll()
            // .antMatchers(HttpMethod.POST, "/logout").authenticated()
            // 所有 /login 的POST请求 都放行
            .antMatchers(HttpMethod.POST, "/login/**").permitAll()
    
            .antMatchers(HttpMethod.GET, "/token/**").permitAll()
            // 所有请求需要身份认证
            .anyRequest().authenticated().and()
            // 添加一个过滤器 所有访问 /login 的请求交给 JWTLoginFilter 来处理 这个类处理所有的JWT相关内容
            .addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
                    UsernamePasswordAuthenticationFilter.class)
            // 添加一个过滤器验证其他请求的Token是否合法
            .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 使用自定义身份验证组件
            auth.authenticationProvider(new CustomAuthenticationProvider());
        }
        @Bean
        AuthenticationEntryPoint myEntryPoint() {
            return new ExampleAuthenticationEntryPoint();
        }
    
    }

    JWT认证登录、鉴权

    public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
    
        public JWTLoginFilter(String url, AuthenticationManager authManager) {
            super(new AntPathRequestMatcher(url));
            setAuthenticationManager(authManager);
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse res)
                throws AuthenticationException, IOException, ServletException {
            try {
                AccountCredentials creds = new AccountCredentials();
                if (creds == null || StringUtils.isEmpty(creds.getUsername()) || StringUtils.isEmpty(creds.getPassword())) {
                    String result = JSONResult.fillResultString(HttpServletResponse.SC_BAD_REQUEST, "请求参数无效", null);
                    res.setContentType("application/json;charset=UTF-8");
                    res.getWriter().println(result);
                }
                return getAuthenticationManager()
                        .authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(), creds.getPassword()));
            } catch (JsonMappingException ex) {
                String result = JSONResult.fillResultString(1, "[参数异常]", null);
                res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                res.getWriter().println(result);
                res.getWriter().close();
            } catch (Exception e) {
                e.printStackTrace();
                String result = JSONResult.fillResultString(10006, "鉴权失败,请重新登录", null);
                res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                res.getWriter().println(result);
                res.getWriter().close();
            }
            return null;
        }
        @Override
        protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                Authentication auth) throws IOException, ServletException {
            TokenAuthenticationService.addAuthentication(res, auth);//响应返回JWT Token
            clearAuthenticationAttributes(req);
    
        }
        @Override
        protected void unsuccessfulAuthentication(HttpServletRequest req, HttpServletResponse res,
                AuthenticationException failed) throws IOException, ServletException {
            SecurityContextHolder.clearContext();
            res.setContentType("application/json;charset=UTF-8");
            res.setStatus(HttpServletResponse.SC_OK);
            String result = JSONResult.fillResultString(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error!!", null);
            res.getWriter().println(result);
            res.getWriter().close();
        }
        protected final void clearAuthenticationAttributes(HttpServletRequest req) {
            HttpSession session = req.getSession(false);
            if(session == null){
                return;
            }
            session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
        }
    }

    认证用户名和密码:

    @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            // 获取认证的用户名 & 密码
            String userName = authentication.getName();
            String passWord = authentication.getCredentials().toString();
            if (loginService == null) {
                loginService = SpringContextUtil.getBean("loginService");
            }
            HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
            String appId = request.getHeader("appId");
            AccountCredentials user = loginService.login(userName, passWord,appId);
            // 认证逻辑
            if (user != null) {
    
                // // 这里设置权限和角色
                // ArrayList<GrantedAuthority> authorities = new ArrayList<>();
                // authorities.add( new GrantedAuthorityImpl("ROLE_ADMIN") );
                // // authorities.add( new GrantedAuthorityImpl("AUTH_WRITE") );
                // // 生成令牌
                // Authentication auth = new UsernamePasswordAuthenticationToken(name, password,
                // authorities);
                // 生成令牌
                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userName, passWord);
                auth.setDetails(user);
                return auth;
            } else {
                 throw new BadCredentialsException("密码错误~");
            }
        }

    demo源码下载链接:https://pan.baidu.com/s/1kWYeBUR,密码:plr4

  • 相关阅读:
    tensorflow版的bvlc模型
    caffe中关于数据进行预处理的方式
    使用caffe时遇到的问题
    关于梯度下降算法的的一些总结
    机器学习优化算法之爬山算法小结
    机器学习之最小二乘法
    机器学习之KNN算法思想及其实现
    安装Python时遇到如下问题,解决方案
    机器学习之决策树熵&信息增量求解算法实现
    朴素贝叶斯分类器(离散型)算法实现(一)
  • 原文地址:https://www.cnblogs.com/ouyanxia/p/8442607.html
Copyright © 2020-2023  润新知