SpringSecurity之微服务权限方案

SpringSecurity之微服务权限方案

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

1. 认证授权过程分析

(1)如果是基于 Session,那么 Spring-security 会对 cookie里的 sessionid进行解析,找到服务器存储的 session信息,然后判断当前用户是否符合请求的要求。
(2)如果是 token,则是解析出 token,然后将当前请求加入到 Spring-security 管理的权限信息中去
(3)认证授权流程图 在这里插入图片描述
说明:
如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于 token 的形式进行授权与认证。
① 用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为 key,权限列表为 value 的形式存入 redis 缓存中
② 根据用户名相关信息生成 token 返回,浏览器将 token 记录到 cookie 中,每次调用 api 接口都默认将 token 携带到 header 请求头中
③ Spring-security 解析 header 头获取 token 信息,解析 token 获取当前用户名
④ 根据用户名就可以从 redis 中获取权限列表,这样 Spring-security 就能够判断当前 请求是否有权限访问

2 权限管理数据模型

三个对象,五张表
在这里插入图片描述

3 JWT介绍

(1)JWT是随机生成的字符串标识,它由三部分组成:头信息,有效载荷(包括用户信息)和签名哈希,三者之间通过 . 进行分隔
在这里插入图片描述

二 具体代码实现

1 项目结构图

在这里插入图片描述

2 编写核心安全配置类

Spring Security 的核心配置就是继承 WebSecurityConfigurerAdapter 并注解 @EnableWebSecurity 的配置。这个配置指明了用户名密码的处理方式、请求路径、登录 登出控制等和安全相关的配置

@Configuration 
@EnableWebSecurity 
@EnableGlobalMethodSecurity(prePostEnabled = true) 
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter{     
	// 自定义查询数据库用户名密码和权限信息     
	private UserDetailsService userDetailsService;     
	//token 管理工具类(生成 token )     
	private TokenManager tokenManager;     
	// 密码管理工具类     
	private DefaultPasswordEncoder defaultPasswordEncoder;     
	//redis 操作工具类     
	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;     
	}     
	/**      
	* 配置设置      
	* */     
	// 设置退出的地址和 token , redis 操作地址    
	 @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();     
	 	}     
	 	/**      
	 	* 密码处理 
	 	* */     
	 	@Override     
	 	public void configure(AuthenticationManagerBuilder auth) throws Exception {         
	 			
	 		auth.userDetailsService(userDetailsService)
	 		.passwordEncoder(defaultPasswordEnc oder);     
	 	}     
	 	/**      
	 	* 配置哪些请求不拦截      
	 	* */     
	 	@Override     
	 	public void configure(WebSecurity web) throws Exception {         
	 		web.ignoring()
	 			.antMatchers("/api/**" , "/swagger-ui.html/**);    
	 	} 
} 
3 创建认证授权相关的工具类

在这里插入图片描述
(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) { 
    }     
    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 操作的工具类

@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()
			.getSu bject();        
		 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) {         
		String token = request.getHeader("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 ());     
	} 
}
4 创建认证授权实体类

在这里插入图片描述
(1)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;     
 		} 
 } 

(2)User实体类

@Data 
@ApiModel(description = "用户实体类") 
public class User implements Serializable {    
	private String username;    
	private String password;    
	private String nickName;    
	private String salt; //加密的盐值   
	private String token; //token字符串
}
5 创建认证和授权的filter

在这里插入图片描述
(1))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);         
 		}     
	} 
 
    /**      
    * 登录成功      
    * */     
    @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));     
    }     
    /**      
    * 登录失败      
    * */     
    @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 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;     
	} 
} 
6 UserDetailsService
@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.atguigu.security.entity.User curUser = new com.atguigu.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;
    }
}
7 整合权限管理

有关具体实现参考项目源码

8 整合网关
  1. 配置文件
# 端口号
server.port=8222
# 服务名
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-acl
# 设置路由uri  lb://注册服务名称
spring.cloud.gateway.routes[0].uri=lb://service-acl
# 具体路径规则
spring.cloud.gateway.routes[0].predicates= Path=/*/acl/**
  1. 主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}
  1. 解决跨域
@Configuration
public class CorsConfig {

    //解决跨域
    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**",config);

        return new CorsWebFilter(source);
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值