springboot整合security(二)微服务案例

一.环境准备

在这里插入图片描述
在这里插入图片描述

创建工程,结构如下:在这里插入图片描述
父工程pom:

<properties>
        <java.version>1.8</java.version>
        <mybatis-plus.version>3.0.5</mybatis-plus.version>
        <velocity.version>2.0</velocity.version>
        <swagger.version>2.7.0</swagger.version>
        <jwt.version>0.7.0</jwt.version>
        <fastjson.version>1.2.28</fastjson.version>
        <gson.version>2.8.2</gson.version>
        <json.version>20170516</json.version>
        <cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!--Spring Cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--mybatis-plus 持久层-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>

            <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>${velocity.version}</version>
            </dependency>

            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>${gson.version}</version>
            </dependency>
            <!--swagger-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!--swagger ui-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!-- JWT -->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
            <dependency>
                <groupId>org.json</groupId>
                <artifactId>json</artifactId>
                <version>${json.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

common模块pom:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided </scope>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <scope>provided </scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <scope>provided </scope>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- spring2.X集成redis所需common-pool2-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

common模块下的spring_secutiry模块pom:


    <dependencies>
        <dependency>
            <groupId>com.study</groupId>
            <artifactId>service_base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring Security依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
    </dependencies>

在这里插入图片描述
在这里插入图片描述

service模块pom:


    <dependencies>

        <dependency>
            <groupId>com.study</groupId>
            <artifactId>service_base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!--服务注册-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--服务调用-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

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

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
        </dependency>


        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

在这里插入图片描述
在这里插入图片描述

二.编写代码

1.common模块编写

1)service_base模块

在这里插入图片描述

@ControllerAdvice //此注解用来实现全局异常处理、全局数据处理、全局数据预处理
@Slf4j
public class GlobalExceptionHandler {
    
     //全局异常类

    //指定出现什么异常执行这个方法
    @ExceptionHandler(Exception.class)
    @ResponseBody //为了返回数据
    public R error(Exception e) {
    
    
        e.printStackTrace();
        return R.error().message("执行了全局异常处理..");
    }

    //特定异常
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody //为了返回数据
    public R error(ArithmeticException e) {
    
    
        e.printStackTrace();
        return R.error().message("执行了ArithmeticException异常处理..");
    }

    //自定义异常
    @ExceptionHandler(GuliException.class)
    @ResponseBody //为了返回数据
    public R error(GuliException e) {
    
    
        log.error(e.getMessage());
        e.printStackTrace();

        return R.error().code(e.getCode()).message(e.getMsg());
    }

}

在这里插入图片描述

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
     //MetaObjectHandler是Mybatis-plus的公共字段更新类
    @Override
    public void insertFill(MetaObject metaObject) {
    
    
        //属性名称,不是字段名称
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
    
    
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }
}
/MD5工具
public final class MD5 {
    
    

    public static String encrypt(String strSrc) {
    
    
        try {
    
    
            char hexChars[] = {
    
     '0', '1', '2', '3', '4', '5', '6', '7', '8',
                    '9', 'a', 'b', 'c', 'd', 'e', 'f' };
            byte[] bytes = strSrc.getBytes();
            //获取MessageDigest信息摘要对象
            MessageDigest md = MessageDigest.getInstance("MD5");  //提供信息摘要算法的功能,它接收任意大小的数据,输出固定长度的哈希值
            //更新摘要
            md.update(bytes);
            //获取哈希值,在MD5算法这,得到的目标字节数组的特点:长度固定为16
            bytes = md.digest();
            int j = bytes.length;
            char[] chars = new char[j * 2];
            int k = 0;
            for (byte b : bytes) {
    
    
                //取高四位
                chars[k++] = hexChars[b >>> 4 & 0xf];
                //取低四位
                chars[k++] = hexChars[b & 0xf];
            }
            return new String(chars);
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
            throw new RuntimeException("MD5加密出错!!+" + e);
        }
    }

    public static void main(String[] args) {
    
    
        System.out.println(MD5.encrypt("111111"));
    }

}

在这里插入图片描述

//统一返回结果的类
@Data
public class R {
    
    

    private Boolean success;

    private Integer code;

    private String message;

    private Map<String, Object> data = new HashMap<String, Object>();

    //把构造方法私有
    private R() {
    
    }

    //成功静态方法
    public static R ok() {
    
    
        R r = new R();
        r.setSuccess(true);
        r.setCode(20000);
        r.setMessage("成功");
        return r;
    }

    //失败静态方法
    public static R error() {
    
    
        R r = new R();
        r.setSuccess(false);
        r.setCode(20001);
        r.setMessage("失败");
        return r;
    }

    public R success(Boolean success){
    
    
        this.setSuccess(success);
        return this;
    }

    public R message(String message){
    
    
        this.setMessage(message);
        return this;
    }

    public R code(Integer code){
    
    
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value){
    
    
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> map){
    
    
        this.setData(map);
        return this;
    }
}

public class ResponseUtil {
    
    

    public static void out(HttpServletResponse response, R r) {
    
    
        //json与对象互换
        ObjectMapper mapper = new ObjectMapper();
        response.setStatus(HttpStatus.OK.value());
        //返回json/utf8格式,过期不影响使用
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
    
    
            mapper.writeValue(response.getWriter(), r);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

@EnableCaching //开启缓存
@Configuration  //配置类
public class RedisConfig extends CachingConfigurerSupport {
    
    

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    
    
        //RedisTemplate用于对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //string序列化
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        //json序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        //可见性
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        已过期
//        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //新指定序列化方法
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
    
    
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        //可见性
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //指定序列化类型,否则序列化到redis没有类型,此方法已过期
//        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //新指定序列化方法
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}
@Configuration//配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {
    
    

    @Bean
    public Docket webApiConfig(){
    
    
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
//               错误路径不监控
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();

    }

    private ApiInfo webApiInfo(){
    
    

        return new ApiInfoBuilder()
                .title("API文档")
                .description("微服务接口定义")
                .version("1.0")
                .contact(new Contact("java", "http://xxx.com", "[email protected]"))
                .build();
    }
}

2)service_security模块
在这里插入图片描述

/**
 * 密码加密和校验
 */
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
    
     //实现密码编码接口

    public DefaultPasswordEncoder() {
    
    
        this(-1);
    }

    public DefaultPasswordEncoder(int length) {
    
    

    }

    //进行MD5加密
    @Override
    public String encode(CharSequence charSequence) {
    
    
        return MD5.encrypt(charSequence.toString());
    }

    //密码比对
    @Override
    public boolean matches(CharSequence charSequence, String ecodePassword) {
    
    
        return ecodePassword.equals(MD5.encrypt(charSequence.toString()));
    }
}
/**
 * token管理工具
 */
@Component
public class TokenManager {
    
    

    //token有效时长
    private long tokenEcpiration=24*60*60*1000;

    //编码密钥
    private String tokenSignkey="12345";

    //使用JWT根据用户名生成token
    public String createToken(String username){
    
    
        String token= Jwts.builder().setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration))
                .signWith(SignatureAlgorithm.HS512,tokenSignkey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }

    //根据token字符串得到用户信息
    public String getUserInfoFromToken(String token){
    
    
        String userInfo = Jwts.parser().setSigningKey(tokenSignkey).parseClaimsJws(token).getBody().getSubject();
        return userInfo;
    }

    //删除token
    public void removeToken(String token){
    
    

    }
}
//退出处理器
public class TokenLogoutHandler implements LogoutHandler {
    
    

    private TokenManager tokenManager;

    private RedisTemplate redisTemplate;

    public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate){
    
    
        this.tokenManager=tokenManager;
        this.redisTemplate=redisTemplate;
    }


    @Override
    public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
    
    
        //从header里面获取token
        String token=httpServletRequest.getHeader("token");

        //token不为空,移出token,从redis删除token
        if (token!=null) {
    
    
            tokenManager.removeToken(token);
            //从token获取用户名
            String username=tokenManager.getUserInfoFromToken(token);
            redisTemplate.delete(username);
        }

        ResponseUtil.out(httpServletResponse, R.ok());

    }
}
//未授权统一处理类
public class UnathEntryPoint implements AuthenticationEntryPoint {
    
    
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
    
    
        ResponseUtil.out(httpServletResponse, R.error());
    }
}

在这里插入图片描述

/**
 * 认证过滤器
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    
    

    private TokenManager tokenManager;

    private RedisTemplate redisTemplate;

    //security封装的权限管理类
    private AuthenticationManager authenticationManager;


    public TokenLoginFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate){
    
    
        this.authenticationManager=authenticationManager;
        this.tokenManager=tokenManager;
        this.redisTemplate=redisTemplate;
        //提交方式设置
        this.setPostOnly(false);
        //访问路径
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    }

    //获取表单提交用户名和密码
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)  {
    
    
       //获取表单提交数据
        try {
    
    
            User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),
                    new ArrayList<>()));
        } catch (IOException e) {
    
    
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    //认证成功调用的方法
    @Override
    public void successfulAuthentication(HttpServletRequest request,
                                         HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    
    
        //得到用户信息
        SecurityUser user = (SecurityUser) authResult.getPrincipal();
        //根据用户名生成token
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        //把用户名和用户权限列表放到redis
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
        //返回token
        ResponseUtil.out(response, R.ok().data("token",token));

    }

    //认证失败调用的方法
    @Override
    public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
    
    
       ResponseUtil.out(response,R.error());
    }

}
/**
 * 授权过滤器
 */
public class TokenAuthFilter extends BasicAuthenticationFilter {
    
    

    private TokenManager tokenManager;

    private RedisTemplate redisTemplate;

    public TokenAuthFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate) {
    
    
        super(authenticationManager);
        this.tokenManager=tokenManager;
        this.redisTemplate=redisTemplate;
    }

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    
    
       //获取当前认证成功用户权限信息
        UsernamePasswordAuthenticationToken authRequest=getAuthentication(request);

        //权限信息存在则放至上下文中
        if (authRequest!=null) {
    
    
            SecurityContextHolder.getContext().setAuthentication(authRequest);
        }
        chain.doFilter(request,response);

    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){
    
    
        //从header获取token
        String token = request.getHeader("token");
        if (token!=null) {
    
    
            //获取用户名
            String username = tokenManager.getUserInfoFromToken(token);

            //从redis获取对应权限列表
            List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(username);
            Collection<GrantedAuthority> authority=new ArrayList<>();
            for (String permissionValue : permissionValueList) {
    
    
                SimpleGrantedAuthority auth = new SimpleGrantedAuthority((permissionValue));
                authority.add(auth);


            }
            return new UsernamePasswordAuthenticationToken(username,token,authority);

        }

        return  null;
    }
}

在这里插入图片描述

/**
 * 用户信息类
 */
@Data
public class SecurityUser implements UserDetails {
    
     //UserDetails是用户信息核心接口

    //当前登录用户
    private transient User currentUserInfo;

    //当前权限
    private List<String> permissionValueList;

    public SecurityUser() {
    
    
    }

    public SecurityUser(User user) {
    
    
        if (user!=null) {
    
    
            this.currentUserInfo=user;
        }
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
     //返回权限集
        //GrantedAuthority权限接口,实际上是一串字符串
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        for(String permissionValue : permissionValueList) {
    
    
            if(StringUtils.isEmpty(permissionValue)) continue;
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
            authorities.add(authority);
        }

        return authorities;
    }

    @Override
    public String getPassword() {
    
    
        return currentUserInfo.getPassword();
    }

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

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

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

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

    @Override
    public boolean isEnabled() {
    
    
        return true;
    }
}

在这里插入图片描述

/**
 * security配置类
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    private TokenManager tokenManager;

    private RedisTemplate redisTemplate;

    private DefaultPasswordEncoder defaultPasswordEncoder;

    private UserDetailsService userDetailsService;

    @Autowired
    public TokenWebSecurityConfig(UserDetailsService userDetailsService,DefaultPasswordEncoder defaultPasswordEncoder,
                                  TokenManager tokenManager,RedisTemplate redisTemplate) {
    
    
        this.userDetailsService=userDetailsService;
        this.defaultPasswordEncoder=defaultPasswordEncoder;
        this.tokenManager=tokenManager;
        this.redisTemplate=redisTemplate;
    }

    /**
     * 配置设置:设置退出的地址和token,redis操作地址
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
       http.exceptionHandling()
               .authenticationEntryPoint(new UnathEntryPoint())
               .and().csrf().disable()
               .authorizeRequests()
               .anyRequest().authenticated()
               .and().logout().logoutUrl("/admin/study/index/logout")
               .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()
               .addFilter(new TokenLoginFilter(authenticationManager(),tokenManager,redisTemplate))
               .addFilter(new TokenAuthFilter(authenticationManager(),tokenManager,redisTemplate)).httpBasic();
    }

    //调用userDetailsService和密码处理
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }

    //不进行认证的路径,可以直接访问
    @Override
    public void configure(WebSecurity web){
    
    
        web.ignoring().antMatchers("/api/**");
    }
}

2.service模块编写

1)service_security模块
在这里插入图片描述

server.port=8080

#服务名称不能用下划线
spring.application.name=service-security

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123

spring.redis.host=192.168.1.102
spring.redis.port=6379
spring.redis.timeout=180000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

mybatis-plus.mapper-locations=classpath:com/study/securityservice/mapper/xml/*.xml

mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl


在这里插入图片描述
在这里插入图片描述

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
    
    

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        //根据用户名查询数据
        User user = userService.selectByUsername(username);
        //判断
        if(user == null) {
    
    
            throw new UsernameNotFoundException("用户不存在");
        }
        com.study.security.entity.User curUser = new com.study.security.entity.User();
        BeanUtils.copyProperties(user,curUser);

        //根据用户查询用户权限列表
        List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());
        SecurityUser securityUser = new SecurityUser();
        securityUser.setCurrentUserInfo(curUser);
        securityUser.setPermissionValueList(permissionValueList);
        return securityUser;
    }
}
@Service
public class IndexServiceImpl implements IndexService {
    
    

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据用户名获取用户登录信息
     *
     * @param username
     * @return
     */
    public Map<String, Object> getUserInfo(String username) {
    
    
        Map<String, Object> result = new HashMap<>();
        User user = userService.selectByUsername(username);
        if (null == user) {
    
    
            //throw new GuliException(ResultCodeEnum.FETCH_USERINFO_ERROR);
        }

        //根据用户id获取角色
        List<Role> roleList = roleService.selectRoleByUserId(user.getId());
        List<String> roleNameList = roleList.stream().map(item -> item.getRoleName()).collect(Collectors.toList());
        if(roleNameList.size() == 0) {
    
    
            //前端框架必须返回一个角色,否则报错,如果没有角色,返回一个空角色
            roleNameList.add("");
        }

        //根据用户id获取操作权限值
        List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());
        redisTemplate.opsForValue().set(username, permissionValueList);

        result.put("name", user.getUsername());
        result.put("avatar", "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
        result.put("roles", roleNameList);
        result.put("permissionValueList", permissionValueList);
        return result;
    }

    /**
     * 根据用户名获取动态菜单
     * @param username
     * @return
     */
    public List<JSONObject> getMenu(String username) {
    
    
        User user = userService.selectByUsername(username);

        //根据用户id获取用户菜单权限
        List<JSONObject> permissionList = permissionService.selectPermissionByUserId(user.getId());
        return permissionList;
    }


}
@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements PermissionService {
    
    

    @Autowired
    private RolePermissionService rolePermissionService;
    
    @Autowired
    private UserService userService;
    
    //获取全部菜单
    @Override
    public List<Permission> queryAllMenu() {
    
    

        QueryWrapper<Permission> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");
        List<Permission> permissionList = baseMapper.selectList(wrapper);

        List<Permission> result = bulid(permissionList);

        return result;
    }

    //根据角色获取菜单
    @Override
    public List<Permission> selectAllMenu(String roleId) {
    
    
        List<Permission> allPermissionList = baseMapper.selectList(new QueryWrapper<Permission>().orderByAsc("CAST(id AS SIGNED)"));

        //根据角色id获取角色权限
        List<RolePermission> rolePermissionList = rolePermissionService.list(new QueryWrapper<RolePermission>().eq("role_id",roleId));
        //转换给角色id与角色权限对应Map对象
//        List<String> permissionIdList = rolePermissionList.stream().map(e -> e.getPermissionId()).collect(Collectors.toList());
//        allPermissionList.forEach(permission -> {
    
    
//            if(permissionIdList.contains(permission.getId())) {
    
    
//                permission.setSelect(true);
//            } else {
    
    
//                permission.setSelect(false);
//            }
//        });
        for (int i = 0; i < allPermissionList.size(); i++) {
    
    
            Permission permission = allPermissionList.get(i);
            for (int m = 0; m < rolePermissionList.size(); m++) {
    
    
                RolePermission rolePermission = rolePermissionList.get(m);
                if(rolePermission.getPermissionId().equals(permission.getId())) {
    
    
                    permission.setSelect(true);
                }
            }
        }


        List<Permission> permissionList = bulid(allPermissionList);
        return permissionList;
    }

    //给角色分配权限
    @Override
    public void saveRolePermissionRealtionShip(String roleId, String[] permissionIds) {
    
    

        rolePermissionService.remove(new QueryWrapper<RolePermission>().eq("role_id", roleId));

  

        List<RolePermission> rolePermissionList = new ArrayList<>();
        for(String permissionId : permissionIds) {
    
    
            if(StringUtils.isEmpty(permissionId)) continue;
      
            RolePermission rolePermission = new RolePermission();
            rolePermission.setRoleId(roleId);
            rolePermission.setPermissionId(permissionId);
            rolePermissionList.add(rolePermission);
        }
        rolePermissionService.saveBatch(rolePermissionList);
    }

    //递归删除菜单
    @Override
    public void removeChildById(String id) {
    
    
        List<String> idList = new ArrayList<>();
        this.selectChildListById(id, idList);

        idList.add(id);
        baseMapper.deleteBatchIds(idList);
    }

    //根据用户id获取用户菜单
    @Override
    public List<String> selectPermissionValueByUserId(String id) {
    
    

        List<String> selectPermissionValueList = null;
        if(this.isSysAdmin(id)) {
    
    
            //如果是系统管理员,获取所有权限
            selectPermissionValueList = baseMapper.selectAllPermissionValue();
        } else {
    
    
            selectPermissionValueList = baseMapper.selectPermissionValueByUserId(id);
        }
        return selectPermissionValueList;
    }

    @Override
    public List<JSONObject> selectPermissionByUserId(String userId) {
    
    
        List<Permission> selectPermissionList = null;
        if(this.isSysAdmin(userId)) {
    
    
            //如果是超级管理员,获取所有菜单
            selectPermissionList = baseMapper.selectList(null);
        } else {
    
    
            selectPermissionList = baseMapper.selectPermissionByUserId(userId);
        }

        List<Permission> permissionList = PermissionHelper.bulid(selectPermissionList);
        List<JSONObject> result = MemuHelper.bulid(permissionList);
        return result;
    }

    /**
     * 判断用户是否系统管理员
     * @param userId
     * @return
     */
    private boolean isSysAdmin(String userId) {
    
    
        User user = userService.getById(userId);

        if(null != user && "admin".equals(user.getUsername())) {
    
    
            return true;
        }
        return false;
    }

    /**
     *	递归获取子节点
     * @param id
     * @param idList
     */
    private void selectChildListById(String id, List<String> idList) {
    
    
        List<Permission> childList = baseMapper.selectList(new QueryWrapper<Permission>().eq("pid", id).select("id"));
        childList.stream().forEach(item -> {
    
    
            idList.add(item.getId());
            this.selectChildListById(item.getId(), idList);
        });
    }

    /**
     * 使用递归方法建菜单
     * @param treeNodes
     * @return
     */
    private static List<Permission> bulid(List<Permission> treeNodes) {
    
    
        List<Permission> trees = new ArrayList<>();
        for (Permission treeNode : treeNodes) {
    
    
            if ("0".equals(treeNode.getPid())) {
    
    
                treeNode.setLevel(1);
                trees.add(findChildren(treeNode,treeNodes));
            }
        }
        return trees;
    }

    /**
     * 递归查找子节点
     * @param treeNodes
     * @return
     */
    private static Permission findChildren(Permission treeNode,List<Permission> treeNodes) {
    
    
        treeNode.setChildren(new ArrayList<Permission>());

        for (Permission it : treeNodes) {
    
    
            if(treeNode.getId().equals(it.getPid())) {
    
    
                int level = treeNode.getLevel() + 1;
                it.setLevel(level);
                if (treeNode.getChildren() == null) {
    
    
                    treeNode.setChildren(new ArrayList<>());
                }
                treeNode.getChildren().add(findChildren(it,treeNodes));
            }
        }
        return treeNode;
    }


    //========================递归查询所有菜单================================================
    //获取全部菜单
    @Override
    public List<Permission> queryAllMenuGuli() {
    
    
        //1 查询菜单表所有数据
        QueryWrapper<Permission> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");
        List<Permission> permissionList = baseMapper.selectList(wrapper);
        //2 把查询所有菜单list集合按照要求进行封装
        List<Permission> resultList = bulidPermission(permissionList);
        return resultList;
    }

    //把返回所有菜单list集合进行封装的方法
    public static List<Permission> bulidPermission(List<Permission> permissionList) {
    
    

        //创建list集合,用于数据最终封装
        List<Permission> finalNode = new ArrayList<>();
        //把所有菜单list集合遍历,得到顶层菜单 pid=0菜单,设置level是1
        for(Permission permissionNode : permissionList) {
    
    
            //得到顶层菜单 pid=0菜单
            if("0".equals(permissionNode.getPid())) {
    
    
                //设置顶层菜单的level是1
                permissionNode.setLevel(1);
                //根据顶层菜单,向里面进行查询子菜单,封装到finalNode里面
                finalNode.add(selectChildren(permissionNode,permissionList));
            }
        }
        return finalNode;
    }

    private static Permission selectChildren(Permission permissionNode, List<Permission> permissionList) {
    
    
        //1 因为向一层菜单里面放二层菜单,二层里面还要放三层,把对象初始化
        permissionNode.setChildren(new ArrayList<Permission>());

        //2 遍历所有菜单list集合,进行判断比较,比较id和pid值是否相同
        for(Permission it : permissionList) {
    
    
            //判断 id和pid值是否相同
            if(permissionNode.getId().equals(it.getPid())) {
    
    
                //把父菜单的level值+1
                int level = permissionNode.getLevel()+1;
                it.setLevel(level);
                //如果children为空,进行初始化操作
                if(permissionNode.getChildren() == null) {
    
    
                    permissionNode.setChildren(new ArrayList<Permission>());
                }
                //把查询出来的子菜单放到父菜单里面
                permissionNode.getChildren().add(selectChildren(it,permissionList));
            }
        }
        return permissionNode;
    }

    //============递归删除菜单==================================
    @Override
    public void removeChildByIdGuli(String id) {
    
    
        //1 创建list集合,用于封装所有删除菜单id值
        List<String> idList = new ArrayList<>();
        //2 向idList集合设置删除菜单id
        this.selectPermissionChildById(id,idList);
        //把当前id封装到list里面
        idList.add(id);
        baseMapper.deleteBatchIds(idList);
    }

    //2 根据当前菜单id,查询菜单里面子菜单id,封装到list集合
    private void selectPermissionChildById(String id, List<String> idList) {
    
    
        //查询菜单里面子菜单id
        QueryWrapper<Permission>  wrapper = new QueryWrapper<>();
        wrapper.eq("pid",id);
        wrapper.select("id");
        List<Permission> childIdList = baseMapper.selectList(wrapper);
        //把childIdList里面菜单id值获取出来,封装idList里面,做递归查询
        childIdList.stream().forEach(item -> {
    
    
            //封装idList里面
            idList.add(item.getId());
            //递归查询
            this.selectPermissionChildById(item.getId(),idList);
        });
    }

    //=========================给角色分配菜单=======================
    @Override
    public void saveRolePermissionRealtionShipGuli(String roleId, String[] permissionIds) {
    
    
        //roleId角色id
        //permissionId菜单id 数组形式
        //1 创建list集合,用于封装添加数据
        List<RolePermission> rolePermissionList = new ArrayList<>();
        //遍历所有菜单数组
        for(String perId : permissionIds) {
    
    
            //RolePermission对象
            RolePermission rolePermission = new RolePermission();
            rolePermission.setRoleId(roleId);
            rolePermission.setPermissionId(perId);
            //封装到list集合
            rolePermissionList.add(rolePermission);
        }
        //添加到角色菜单关系表
        rolePermissionService.saveBatch(rolePermissionList);
    }
}
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    
    

    @Autowired
    private UserRoleService userRoleService;


    //根据用户获取角色数据
    @Override
    public Map<String, Object> findRoleByUserId(String userId) {
    
    
        //查询所有的角色
        List<Role> allRolesList =baseMapper.selectList(null);

        //根据用户id,查询用户拥有的角色id
        List<UserRole> existUserRoleList = userRoleService.list(new QueryWrapper<UserRole>().eq("user_id", userId).select("role_id"));

        List<String> existRoleList = existUserRoleList.stream().map(c->c.getRoleId()).collect(Collectors.toList());

        //对角色进行分类
        List<Role> assignRoles = new ArrayList<Role>();
        for (Role role : allRolesList) {
    
    
            //已分配
            if(existRoleList.contains(role.getId())) {
    
    
                assignRoles.add(role);
            }
        }

        Map<String, Object> roleMap = new HashMap<>();
        roleMap.put("assignRoles", assignRoles);
        roleMap.put("allRolesList", allRolesList);
        return roleMap;
    }

    //根据用户分配角色
    @Override
    public void saveUserRoleRealtionShip(String userId, String[] roleIds) {
    
    
        userRoleService.remove(new QueryWrapper<UserRole>().eq("user_id", userId));

        List<UserRole> userRoleList = new ArrayList<>();
        for(String roleId : roleIds) {
    
    
            if(StringUtils.isEmpty(roleId)) continue;
            UserRole userRole = new UserRole();
            userRole.setUserId(userId);
            userRole.setRoleId(roleId);

            userRoleList.add(userRole);
        }
        userRoleService.saveBatch(userRoleList);
    }

    @Override
    public List<Role> selectRoleByUserId(String id) {
    
    
        //根据用户id拥有的角色id
        List<UserRole> userRoleList = userRoleService.list(new QueryWrapper<UserRole>().eq("user_id", id).select("role_id"));
        List<String> roleIdList = userRoleList.stream().map(item -> item.getRoleId()).collect(Collectors.toList());
        List<Role> roleList = new ArrayList<>();
        if(roleIdList.size() > 0) {
    
    
            roleList = baseMapper.selectBatchIds(roleIdList);
        }
        return roleList;
    }
}

3…infrastrucutre模块编写

1)api_gateway模块
在这里插入图片描述

#配置文件

server.port=8090

spring.application.name=service-gateway

#nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true

#配置路由规则
spring.cloud.gateway.routes[0].id=service-security
#设置路由url--lb://注册服务名
spring.cloud.gateway.routes[0].uri=lb://service-security
#具体路径规则
spring.cloud.gateway.routes[0].predicates= Path=/*/acl/**

在这里插入图片描述

/**
 * 解决跨域问题配置类
 */
@Configuration
public class CorsConfig {
    
    

    @Bean
    public CorsWebFilter corsWebFilter(){
    
    
        CorsConfiguration config = new CorsConfiguration();
        //允许所有方法
        config.addAllowedMethod("*");
        //允许所有域名
        config.addAllowedOrigin("*");
        //允许所有Header
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource(new PathPatternParser());
        //配置可以访问的地址
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

猜你喜欢

转载自blog.csdn.net/worilb/article/details/112435787