zoukankan      html  css  js  c++  java
  • SpringCloud整合SpringSecurity JWT进行认证 ,鉴权

    一. 创建认证微服务AuthenticationService

    1.1 pom.xml

    点击查看代码
    <dependencies>
            <!--mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!--mybatis-dynamic-sql , 可以使用mybatis plus或者自己写sql-->
            <dependency>
                <groupId>org.mybatis.dynamic-sql</groupId>
                <artifactId>mybatis-dynamic-sql</artifactId>
                <version>1.2.1</version>
            </dependency>
            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.4</version>
            </dependency>
            <!-- lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <!-- jwt -->
            <dependency>
                <groupId>com.nimbusds</groupId>
                <artifactId>nimbus-jose-jwt</artifactId>
            </dependency>
            <!-- ssm spring boot -->
            <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-data-redis</artifactId>
            </dependency>
            <!-- spring cloud -->
            <!-- spring cloud alibba -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <!-- test -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${spring-cloud-alibaba.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>

    1.2 创建SimpleUserDetailsService 实现 UserDetailsService接口

    作用:将数据查到的用户信息和权限放进UserDetails对象,用于SpringSecurity进行认证

    @Component
    @Slf4j
    public class SimpleUserDetailsService implements UserDetailsService {
        @Autowired
        private UserService userService;
        private final PasswordEncoder passwordEncoder;
    
        public SimpleUserDetailsService(PasswordEncoder passwordEncoder) {
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            //从数据库获取用户信息
            UserEntity userEntity = userService.getByUsername(s);
            //从数据库获取用户的角色权限
            List<UserRoles> userRolesList = userService.getRolesByUsername(s);
            StringBuilder authorityBuilder = new StringBuilder();
            //将角色信息放进StringBuilder中
            userRolesList.forEach(r-> {
                authorityBuilder.append("ROLE_").append(r.getRoleName().toUpperCase()).append(",");
                //从数据库获取用户资源权限
                List<RolePermissions> permissionsList = userService.getPermissionsByRole(r.getRoleName());
                permissionsList.forEach(p->authorityBuilder.append(p.getPermission()).append(","));
            });
            //获取用户密码
            String password = userEntity.getPassword();
            log.info("password->"+password);
            log.info("authorities->"+authorityBuilder.toString());
            //返回UserDetails对象
            return new User(s,passwordEncoder.encode(password), AuthorityUtils.commaSeparatedStringToAuthorityList(authorityBuilder.toString()));
        }
    }

    1.3 创建JWTAuthenticationSuccessHandler 实现 AuthenticationSuccessHandler接口

    作用:自定义登录成功请求返回的结果,SpringSecurity默认登录成功后跳转到登录前url或者"/",前后端分离项目需要登录成功后返回jwt-token

    @Component
    public class JWTAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
        //json
        private final ObjectMapper objectMapper = new ObjectMapper();
        //操作redis
        private final HashOperations<String, String, String> operations;
    
        // 构造注入
        public JWTAuthenticationSuccessHandler(RedisTemplate<String, String> redisTemplate) {
            this.operations = redisTemplate.opsForHash();
        }
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,
                                            Authentication authentication)
                throws IOException, ServletException {
    
            // authentication 对象携带了当前登陆用户名等相关信息
            User user = (User) authentication.getPrincipal();
            resp.setContentType("application/json;charset=UTF-8");
            try {
                StringBuffer buffer = new StringBuffer();
                user.getAuthorities().forEach(item -> {
                    buffer.append(item.getAuthority());
                    buffer.append(",");
                });
                buffer.deleteCharAt(buffer.length()-1);
    
                // 用户的 username 和他所具有的权限存入 redis 中。
                operations.put(JWTUtil.REDIS_HASH_KEY, user.getUsername(), buffer.toString());
    
                // 在 jwt-token-string 的荷载(payload)中存上当前用户的名字.
                String jwtStr = JWTUtil.createJWT(user.getUsername());
    
                Map<String, String> map = new HashMap<>();
                map.put("code", "10000");
                map.put("msg", "success");
                map.put("jwt-token", jwtStr);
    
                PrintWriter out = resp.getWriter();
                out.write(objectMapper.writeValueAsString(map));
                out.flush();
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    1.4 配置SpringSecurity,将JWTAuthenticationSuccessHandler的返回结果替换默认返回结果

    @EnableWebSecurity(debug = false)
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Resource
        private SimpleUserDetailsService userDetailsService;
    
        @Resource
        private JWTAuthenticationSuccessHandler successHandler;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();   // 这是一个空的、假的密码加密器。在加密时啥事没干。
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //任何请求都需要认证
            http.authorizeRequests().anyRequest().authenticated(); 
            //登录框登录,登录成功后使用自定义的JWTAuthenticationSuccessHandler
            http.formLogin().successHandler(successHandler);
            //禁用跨域过滤器
            http.csrf().disable();
            //禁用session过滤器
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService)
                    .passwordEncoder(passwordEncoder());
        }
    }

    1.5 将服务注册进nacos

    • 启动类上添加@EnableDiscoveryClient注解
      @SpringBootApplication
      @EnableDiscoveryClient
      public class AuthenticationServiceApplication {
          public static void main(String[] args) {
              SpringApplication.run(AuthenticationServiceApplication.class, args);
          }
      }
    • 配置bootstrap.yml
      点击查看代码
      spring:
        cloud:
          nacos:
            discovery:
              server-addr: 192.172.0.24:8848
              password: nacos
              username: nacos
              group: Dracarys
            config:
              contextPath: /nacos
              server-addr: ${spring.cloud.nacos.discovery.server-addr}
              username: ${spring.cloud.nacos.discovery.username}
              password: ${spring.cloud.nacos.discovery.password}
              group: ${spring.cloud.nacos.discovery.group}
    • 配置application.yml
      点击查看代码
      server:
        port: 8080
      spring:
        application:
          name: authentication-service
        datasource:
          driver-class-name: com.mysql.cj.jdbc.Driver
          password: 123root456
          url: jdbc:mysql://114.55.6.86:3306/security_db?serverTimezone=UTC
          username: root
        redis:
          host: 114.55.6.86
          port: 6379
          password: 123

    二. 创建普通需要鉴权的微服务SecurityService

    2.1 pom.xml

    点击查看代码
    <dependencies>
            <!--jwt-->
            <dependency>
                <groupId>com.nimbusds</groupId>
                <artifactId>nimbus-jose-jwt</artifactId>
                <version>9.11.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</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>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</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>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>io.projectreactor</groupId>
                <artifactId>reactor-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${spring-cloud-alibaba.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>

    2.2 自定义JwtFilter过滤器,拦截请求,添加UseranmePasswordAuthenticationToken

    @Slf4j
    @Component
    public class JwtFilter extends OncePerRequestFilter {
        //操作redis
        private final HashOperations<String, String, String> operations;
        //构造注入
        public JwtFilter(RedisTemplate<String, String> redisTemplate) {
            this.operations = redisTemplate.opsForHash();
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                        FilterChain filterChain) throws ServletException, IOException {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            //如果security_context_holder中有authentication
            if(authentication != null){
                log.info("security_context_holder中有authentication");
                filterChain.doFilter(request,response);
                return;
            }
    //        String jwtStr = request.getHeader("x-jwt-token");
            String username = request.getHeader("x-username");
            //如果请求头里没有token
            if(StringUtils.isEmpty(username)){
                log.info("没有username");
                filterChain.doFilter(request,response);
                return;
            }
            //jwtStr验证不通过
            /*if(!JwtUtils.verify(jwtStr)){
                log.info("jwtStr验证不通过");
                filterChain.doFilter(request,response);
                return;
            }
            String username = JwtUtils.getUsernameFromJWT(jwtStr);*/
            //根据用户名从redis中获取权限
            log.info("username->"+username);
            String authorities =  operations.get("jwt-token",username);
            log.info("authorities->"+operations.get("jwt-token",username));
            //将权限存进token中
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            filterChain.doFilter(request,response);
        }
    }

    2.3 配置SpringSecurity,将自定义的JwtFilter过滤器添加进SpringSecurity过滤器链

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Resource
        private JwtFilter jwtFilter;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().anyRequest().authenticated()
                    .and()
                    //将自定义的Jwt过滤器添加到UsernamePasswordAuthenticationFilter后面
                    .formLogin().and().addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }

    2.4 将服务注册到Nacos

    • 启动类上添加@EnableDiscoveryClient注解
      @SpringBootApplication
      @EnableDiscoveryClient
      public class SecurityServiceDemo1Application {
          public static void main(String[] args) {
              SpringApplication.run(SecurityServiceDemo1Application.class, args);
          }
      }
    • application.yml
      点击查看代码
      #日志
      logging:
          level:
              root: INFO
              com.wn: DEBUG
          pattern:
              console: "${CONSOLE_LOG_PATTERN:%clr(${LOG_LEVEL_PATTERN:%5p}) %clr(|){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}"
      server:
          port: 9000
      spring:
          application:
              name: security-service-demo1
          cloud:
              nacos:
                  discovery:
                      group: Dracarys
                      namespace: public
                      password: nacos
                      server-addr: 192.172.0.24:8848
                      username: nacos
          redis:
              port: 6379
              host: 114.55.6.86

    三. 创建Gateway微服务

    3.1 pom.xml

    点击查看代码
    <dependencies>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <dependency>
                <groupId>com.nimbusds</groupId>
                <artifactId>nimbus-jose-jwt</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${spring-cloud-alibaba.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>

    3.2 自定义全局过滤器GatewayFilter,将jwt-token解析,返回用户名到请求头

    @Component
    @Slf4j
    public class GatewayFilter implements GlobalFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
            HttpHeaders headers = exchange.getRequest().getHeaders();
            List<String> strings = headers.get("x-jwt-token");
            ServerHttpRequest mutateRequest = exchange.getRequest();
            //校验token,成功拿到token中的username
            if(strings.size()>0 && JWTUtil.verify(strings.get(0))){
                String username = JWTUtil.getUsernameFromJWT(strings.get(0));
                //将username存进请求头
                mutateRequest = exchange.getRequest().mutate().header("x-username", username).build();
                log.info("将用户名存进请求头"+username);
            }
            return chain.filter(exchange.mutate().request(mutateRequest).build());
        }
    }

    3.2 将服务注册到nacos

    • 启动类上添加@EnableDiscoveryClient注解
    • application.yml
      点击查看代码
      #日志
      logging:
        level:
          root: INFO
          com.wn: DEBUG
        pattern:
          console: "${CONSOLE_LOG_PATTERN:%clr(${LOG_LEVEL_PATTERN:%5p}) %clr(|){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}"
      server:
        port: 88
      spring:
        application:
          name: gateway
        cloud:
          nacos:
            discovery:
              server-addr: 192.172.0.24:8848
              username: nacos
              password: nacos
              group: Dracarys
          #整合gateway和openFeign
          gateway:
            discovery:
              locator:
                enabled: true
                lower-case-service-id: true

    四. JwtUtils

    点击查看代码
    package com.wn.service.util;
    
    import com.nimbusds.jose.*;
    import com.nimbusds.jose.crypto.MACSigner;
    import com.nimbusds.jose.crypto.MACVerifier;
    import com.nimbusds.jose.shaded.json.JSONObject;
    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.util.StringUtils;
    
    import java.util.Collection;
    import java.util.Map;
    
    @Slf4j
    public class JwtUtils {
    
        private static final String usernameKey = "username";
        private static final String authoritiesKey = "authorities";
    
        public static final String secret = "hello world goodbye thank you very much see you next time";
    
        static {
            log.info("spring security jwt secret: {}", secret);
        }
        @SneakyThrows
        public static String createJWT(String username) {
    
            // jwt 头
            JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build();
    
            // jwt 荷载
            JSONObject obj = new JSONObject();
            obj.put(usernameKey, username);
            Payload payload = new Payload(obj);
    
            // jwt 头 + 荷载 + 密钥 = 签名
            JWSSigner jwsSigner = new MACSigner(secret);
            JWSObject jwsObject = new JWSObject(jwsHeader, payload);
            // 进行签名(根据前两部分生成第三部分)
            jwsObject.sign(jwsSigner);
    
            // 获得 jwt string
            return jwsObject.serialize();
        }
    
        @SneakyThrows
        public static String createJWT(String username, Collection<? extends GrantedAuthority> authorities) {
    
            // jwt 头
            JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build();
    
            // jwt 荷载
            JSONObject obj = new JSONObject();
            obj.put(usernameKey, username);
            obj.put(authoritiesKey, StringUtils.collectionToCommaDelimitedString(authorities));  // "xxx,yyy,zzz,..."
            Payload payload = new Payload(obj);
    
            // jwt 头 + 荷载 + 密钥 = 签名
            JWSSigner jwsSigner = new MACSigner(secret);
            JWSObject jwsObject = new JWSObject(jwsHeader, payload);
            // 进行签名(根据前两部分生成第三部分)
            jwsObject.sign(jwsSigner);
    
            // 获得 jwt string
            return jwsObject.serialize();
        }
    
        @SneakyThrows
        public static boolean verify(String jwtString) {
            JWSObject jwsObject = JWSObject.parse(jwtString);
            JWSVerifier jwsVerifier = new MACVerifier(secret);
            return jwsObject.verify(jwsVerifier);
        }
    
        @SneakyThrows
        public static String getUsernameFromJWT(String jwtString) {
            JWSObject jwsObject = JWSObject.parse(jwtString);
            Map<String, Object> map = jwsObject.getPayload().toJSONObject();
            return (String) map.get(usernameKey);
        }
    
        @SneakyThrows
        public static String getAuthoritiesFromJwt(String jwtString) {
            JWSObject jwsObject = JWSObject.parse(jwtString);
            Map<String, Object> map = jwsObject.getPayload().toJSONObject();
            return (String) map.get(authoritiesKey);
        }
    
    }
  • 相关阅读:
    最近玩Bootstrap , 一些小工具 记录在案。
    测试word发表博客
    Linux at 定时任务
    Linux查看磁盘目录内存空间使用情况
    R生存分析AFT
    Accelerated Failure Time Models加速失效时间模型AFT
    Shell sleep指定延迟时间
    Shell脚本导入外部脚本内容
    Shell输入输出重定向
    Shell while
  • 原文地址:https://www.cnblogs.com/japhi/p/15634853.html
Copyright © 2011-2022 走看看