SpringSecurity 微服务对权限的整合


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

(1)如果是基于 Session,那么 Spring-security 会对 cookie 里的 sessionid 进行解析,找到服务器存储的 session 信息,然后判断当前用户是否符合请求的要求。
(2)如果是 token,则是解析出 token,然后将当前请求加入到 Spring-security 管理的权限信息中去。

在这里插入图片描述

如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于 token 的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为 key,权限列表为 value 的形式存入 redis 缓存中,根据用户名相关信息生成 token 返回,浏览器将 token 记录到 cookie 中,每次调用 api 接口都默认将 token 携带到 header 请求头中,Spring-security 解析 header 头获取 token 信息,解析 token 获取当前用户名,根据用户名就可以从 redis 中获取权限列表,这样 Spring-security 就能够判断当前请求是否有权限访问。

2、权限管理数据模型

在这里插入图片描述

3、项目结构和功能说明

在这里插入图片描述
项目主要实现的功能

  • 登录(认证)
  • 添加角色
  • 为角色分配权限
  • 添加用户
  • 为用户分配角色

项目涉及技术说明

  1. Maven
    创建父工程:管理项目依赖版本
    创建子模块:使用具体依赖

  2. SpringBoot
    本质就是Spring,是Spring快速构建工具

  3. MyBatisPlus
    操作数据库框架

  4. SpringCloud
    (1)GateWay网关:对外暴露一个统一的端口,通过网关转发到具体模块中去。
    (2)Nacos注册中心:将所有模块都存放在注册中心去,方便服务之间的互相调用。

  5. 其他技术
    Redis、Jwt、Swagger

  6. 前端技术
    vue、elementUI

3、核心业务

1. 代码结构图说明

在这里插入图片描述

2. 创建认证授权相关的工具类

(1)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) {
    
    

    }

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

	//进行密码比对
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
    
    
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }
}

(2)TokenManager:token操作的工具类

使用jwt生成token

@Component
public class TokenManager {
    
    
    //token有效时长
    private long tokenExpiration = 24*60*60*1000;
    //编码秘钥
    private String tokenSignKey = "123456";

    //使用jwt根据和用户名生成token
    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;
    }

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

    public void removeToken(String token) {
    
    
        //jwttoken无需删除,客户端扔掉即可。
    }

}

(3)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) {
    
    
        //从header里面获取token
        String token = request.getHeader("token");
        //token不为空,移除token,从redis删除token
        if (token != null) {
    
    
            //移除
            tokenManager.removeToken(token);

            //清空当前用户缓存中的权限数据
            String userName = tokenManager.getUserFromToken(token);
            redisTemplate.delete(userName);
        }
        ResponseUtil.out(response, R.ok());
    }

}

(4)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. 创建认证授权实体类

(1)User

@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;

}

(2)SecutityUser

@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;
    }
}

4. 创建认证和授权的filter

(1)TokenLoginFilter:认证的filter

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    
    

    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    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 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);
        }
    }

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

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

(2)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 request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
    
    
        //
        logger.info("================="+request.getRequestURI());
        if(request.getRequestURI().indexOf("admin") == -1) {
    
    
            chain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = null;
        try {
    
    
            //获取当前认证成功用户权限信息
            authentication = getAuthentication(request);
        } catch (Exception e) {
    
    
            ResponseUtil.out(response, R.error());
        }

        //判断如果有权限信息,放到权限上下文中
        if (authentication != null) {
    
    
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
    
    
            ResponseUtil.out(response, R.error());
        }
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
    
    
        //从header中获取token
        String token = request.getHeader("token");
        if (token != null && !"".equals(token.trim())) {
    
    
            //从token获取用户名
            String userName = tokenManager.getUserFromToken(token);

            //从redis获取对应权限列表
            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. 创建核心配置类

@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;
    }

    //配置基本设置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.exceptionHandling()
                //没有权限访问
                .authenticationEntryPoint(new UnauthorizedEntryPoint())
                .and().csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                //设置退出路径
                .and().logout().logoutUrl("/admin/acl/index/logout")
                .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()
                //添加自定义过滤器
                .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
                .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    }

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

    //不进行认证的路径,可以直接访问
    @Override
    public void configure(WebSecurity web) throws Exception {
    
    
        web.ignoring().antMatchers("/api/**",
                "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
        //web.ignoring().antMatchers("/*/**");
    }
}

6. 权限认证的实现类

@Service("userDetailsService")
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.kuang.serurity.entity.User curUser = new com.kuang.serurity.entity.User();
        BeanUtils.copyProperties(user,curUser);

        //根据用户查询用户权限列表
        List<String> authorities = permissionService.selectPermissionValueByUserId(user.getId());
        SecurityUser securityUser = new SecurityUser(curUser);
        securityUser.setPermissionValueList(authorities);
        return securityUser;
    }

}

4、整合前端后的效果图


如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客

猜你喜欢

转载自blog.csdn.net/weixin_45606067/article/details/111742560
今日推荐