SpringSecurity微服务架构下的方案

五、SpringSecurity微服务架构下的方案

1、微服务的概述

  1. 微服务概述:
    • 微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,这些服务可以使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
  2. 微服务优势
    • 微服务每个模块就相当于一个单独的项目,代码量明显减少,遇到问题也相对来说比较好解决。
    • 微服务每个模块都可以使用不同的存储方式(比如有的用 redis,有的用 mysql等),数据库也是单个模块对应自己的数据库。
    • 微服务每个模块都可以使用不同的开发技术,开发模式更灵活。
  3. 微服务本质
    • 系统要提供一套基础的架构,这种架构使得微服务可以独立的部署、运行、升级。这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在功能上则表现为一个统一的整体。这种所谓的“统一的整体”表现出来的是统一风格的界面,统一的权限管理,统一的安全策略,统一的上线过程,统一的日志和审计方法,统一的调度方式,统一的访问入口等等。
    • 微服务的目的是有效的拆分应用,实现敏捷开发和部署。

2、微服务认证与授权实现思路

  1. 认证授权过程分析

    • 基于Token,解析出 token,然后将当前请求加入到 Spring-security 管理的权限信息中去
    • 思路流程:因为模块众多每个模块都需要进行授权与认证,所以我们选择基于 token 的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为 key,权限列表为 value 的形式存入 redis 缓存中,根据用户名相关信息生成 token 返回,浏览器将 token 记录到 cookie 中,每次调用 api 接口都默认将 token 携带到 header 请求头中,Spring-security 解析 header 头获取 token 信息,解析 token 获取当前用户名,根据用户名就可以从 redis 中获取权限列表,这样 Spring-security 就能够判断当前请求是否有权限访问
    • 思路流程图:
      • 在这里插入图片描述
  2. 详细代码流程(仅供参考不做解释)

    1. 编写核心配置类

      • @Configuration
        @EnableWebSecurity
        @EnableGlobalMethodSecurity(prePostEnabled = true)
        public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
        
            private UserDetailsService userDetailsService;
            private TokenManager tokenManager;
            private DefaultPasswordEncoder defaultPasswordEncoder;
            private RedisTemplate redisTemplate;
        
            //自动注入一下需要的依赖
            @Autowired
            public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
                                          TokenManager tokenManager, RedisTemplate redisTemplate) {
                this.userDetailsService = userDetailsService;
                this.defaultPasswordEncoder = defaultPasswordEncoder;
                this.tokenManager = tokenManager;
                this.redisTemplate = redisTemplate;
            }
        
            /**
             * 配置设置
             * @param http
             * @throws Exception
             */
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http.exceptionHandling()
                        .authenticationEntryPoint(new UnauthorizedEntryPoint())//无权限处理
                        .and().csrf().disable()//关闭csrf
                        .authorizeRequests()
                        .anyRequest().authenticated()//任何请求都需要验证
                        .and().logout().logoutUrl("/admin/acl/index/logout")//登出的请求路径
                        .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()//登出处理器
                        .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))//重写的UsernamePasswordAuthenticationFilter接口实现类
                        .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();//过滤请求是否包含token
            }
        
            /**
             * 密码处理
             * @param auth
             * @throws Exception
             */
            @Override
            public void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
            }
        
            /**
             * 配置哪些请求不拦截
             * @param web
             * @throws Exception
             */
            @Override
            public void configure(WebSecurity web) throws Exception {
                web.ignoring().antMatchers("/api/**",
                        "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
                       );
            }
        }
        
    2. 相关的工具配置类

      • //DefaultPasswordEncoder:密码处理的方法
        @Component
        public class DefaultPasswordEncoder implements PasswordEncoder {
        
            public DefaultPasswordEncoder() {
                this(-1);
            }
        
            /**
             * @param strength
             *            the log rounds to use, between 4 and 31
             */
            public DefaultPasswordEncoder(int strength) {
        
            }
        
            public String encode(CharSequence rawPassword) {
                return MD5.encrypt(rawPassword.toString());
            }
        
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
            }
        }
        
      • //TokenManager:token 操作的工具类
        @Component
        public class TokenManager {
        
            private long tokenExpiration = 24*60*60*1000;
            private String tokenSignKey = "123456";
        
            public String createToken(String username) {
                String token = Jwts.builder().setSubject(username)
                        .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                        .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
                return token;
            }
        
            public String getUserFromToken(String token) {
                String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
                return user;
            }
        
            public void removeToken(String token) {
                //jwttoken无需删除,客户端扔掉即可。
            }
        
        }
        
      • //TokenLogoutHandler:退出实现
        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 request, HttpServletResponse response, Authentication authentication) {
                String token = request.getHeader("token");
                if (token != null) {
                    tokenManager.removeToken(token);
        
                    //清空当前用户缓存中的权限数据
                    String userName = tokenManager.getUserFromToken(token);
                    redisTemplate.delete(userName);
                }
                ResponseUtil.out(response, R.ok());
            }
        
        }
        
      • //UnauthorizedEntryPoint:未授权统一处理
        public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
        
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response,
                                 AuthenticationException authException) throws IOException, ServletException {
        
                ResponseUtil.out(response, R.error());
            }
        }
        
    3. 创建认证授权实体类

      • @Data
        @Slf4j
        public class SecurityUser implements 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() {
                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;
            }
        }
        
      • package com.qzh.serurity.entity;
        
        import io.swagger.annotations.ApiModel;
        import io.swagger.annotations.ApiModelProperty;
        import lombok.Data;
        
        import java.io.Serializable;
        
        /**
         * <p>
         * 用户实体类
         * </p>
         *
         * @author qy
         * @since 2019-11-08
         */
        @Data
        @ApiModel(description = "用户实体类")
        public class User implements Serializable {
        
        	private static final long serialVersionUID = 1L;
        
        	@ApiModelProperty(value = "微信openid")
        	private String username;
        
        	@ApiModelProperty(value = "密码")
        	private String password;
        
        	@ApiModelProperty(value = "昵称")
        	private String nickName;
        
        	@ApiModelProperty(value = "用户头像")
        	private String salt;
        
        	@ApiModelProperty(value = "用户签名")
        	private String token;
        
        }
        
    4. 创建认证和授权的filter

      • //TokenLoginFilter:认证的 filter
        public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
        
            private AuthenticationManager authenticationManager;
            private TokenManager tokenManager;
            private RedisTemplate redisTemplate;
        
            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 req, HttpServletResponse res)
                    throws AuthenticationException {
                try {
                    User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
        
                    return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        
            /**
             * 登录成功
             * @param req
             * @param res
             * @param chain
             * @param auth
             * @throws IOException
             * @throws ServletException
             */
            @Override
            protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                                    Authentication auth) throws IOException, ServletException {
                SecurityUser user = (SecurityUser) auth.getPrincipal();
                String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
                redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
        
                ResponseUtil.out(res, R.ok().data("token", token));
            }
        
            /**
             * 登录失败
             * @param request
             * @param response
             * @param e
             * @throws IOException
             * @throws ServletException
             */
            @Override
            protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                                      AuthenticationException e) throws IOException, ServletException {
                ResponseUtil.out(response, R.error());
            }
        }
        
      • //TokenAuthenticationFilter:授权 filter
        public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
            private TokenManager tokenManager;
            private RedisTemplate redisTemplate;
        
            public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
                super(authManager);
                this.tokenManager = tokenManager;
                this.redisTemplate = redisTemplate;
            }
        
            @Override
            protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
                    throws IOException, ServletException {
                logger.info("================="+req.getRequestURI());
                if(req.getRequestURI().indexOf("admin") == -1) {
                    chain.doFilter(req, res);
                    return;
                }
        
                UsernamePasswordAuthenticationToken authentication = null;
                try {
                    authentication = getAuthentication(req);
                } catch (Exception e) {
                    ResponseUtil.out(res, R.error());
                }
        
                if (authentication != null) {
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                } else {
                    ResponseUtil.out(res, R.error());
                }
                chain.doFilter(req, res);
            }
        
            private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
                // token置于header里
                String token = request.getHeader("token");
                if (token != null && !"".equals(token.trim())) {
                    String userName = tokenManager.getUserFromToken(token);
        
                    List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
                    Collection<GrantedAuthority> authorities = new ArrayList<>();
                    for(String permissionValue : permissionValueList) {
                        if(StringUtils.isEmpty(permissionValue)) continue;
                        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                        authorities.add(authority);
                    }
        
                    if (!StringUtils.isEmpty(userName)) {
                        return new UsernamePasswordAuthenticationToken(userName, token, authorities);
                    }
                    return null;
                }
                return null;
            }
        }
        
    5. 编写UserDetailsService接口实现类

      • //查询用户具体信息
        @Service
        public class UserDetailsServiceImpl implements UserDetailsService {
        
            @Autowired
            private UserService userService;
        
            @Autowired
            private PermissionService permissionService;
        
            /***
             * 根据账号获取用户信息
             * @param username:
             * @return: org.springframework.security.core.userdetails.UserDetails
             */
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                // 从数据库中取出用户信息
                User user = userService.selectByUsername(username);
        
                // 判断用户是否存在
                if (null == user){
                    //throw new UsernameNotFoundException("用户名不存在!");
                }
                // 返回UserDetails实现类
                com.qzh.serurity.entity.User curUser = new com.qzh.serurity.entity.User();
                BeanUtils.copyProperties(user,curUser);
        
                //从数据库获取用户权限
                List<String> authorities = permissionService.selectPermissionValueByUserId(user.getId());
                SecurityUser securityUser = new SecurityUser(curUser);
                securityUser.setPermissionValueList(authorities);
                return securityUser;
            }
        
        }
        
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值