Distributed permissions shiro + jwt + redis

1. Distributed permission technology selection

Using JWT + Shiro + Redis
about springSecurity can do a lot of distribution. But the shiro I used at the beginning of this project, and the roles and menus corresponding to the permissions have been written, so the main body uses shiro. Use jwt to identify each user's identity. Use redis to store permissions for each user. Then read permissions from redis every time.

2. Understand jwt

JWT consists of 3 parts: header (Header), payload (Payload) and signature (Signature). During transmission, the three parts of the JWT will be Base64-encoded and then concatenated with . to form the final transmitted string.
Put the user's id in the payload section. The user's id can then be parsed from the jwt string.

3. Current login logic

/**
	 * 登录
	 */
	@PostMapping("/sys/login")
	public GenericResponse login(@RequestBody SysLoginForm form)throws IOException {
    
    
		//用户信息
		SysUserEntity user = sysUserService.queryByUserName(form.getUsername());
		CommonUser commonUser = new CommonUser();
		commonUser.setOpenid(user.getOpenid());
		commonUser.setUserId(user.getUserId());
		//账号不存在、密码错误
		if(user == null || !user.getPassword().equals(new Sha256Hash(form.getPassword(), user.getSalt()).toHex())) {
    
    
			return GenericResponse.response(ServiceError.LOGIN_ERROR_USERNAME);

		}
		String token = "";
		try {
    
    
			token = JwtTokenUtil.generateToken(commonUser);
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}

		Set<String> permsSet = shiroService.getUserPermissions(user.getUserId());
		Gson gson = new Gson();
		redisTemplate.opsForValue().set(user.getUserId().toString(), gson.toJson(permsSet));
		return GenericResponse.response(ServiceError.NORMAL, token);
	}

Generate token, store user id and lock permissions into redis

4. The configuration made by shiro

1.ShiroConfig

 */
@Configuration
public class ShiroConfig {
    
    

    @Bean("securityManager")
    public SecurityManager securityManager(OAuth2Realm oAuth2Realm) {
    
    
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(oAuth2Realm);
        securityManager.setRememberMeManager(null);
        return securityManager;
    }



    //============================到目前为止是新加入的东西,具体还需要看效果

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    
    
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        //oauth过滤
        Map<String, Filter> filters = new HashMap<>();
        filters.put("oauth2", new OAuth2Filter());
        shiroFilter.setFilters(filters);

        Map<String, String> filterMap = new LinkedHashMap<>();
        //这里放置自己通行的静态资源
//        filterMap.put("/", "anon");
        filterMap.put("/downloadFile","anon");
        filterMap.put("/parseByName","anon");

        //静态资源不拦截
        filterMap.put("/202108*/**", "anon");
        filterMap.put("/config/**", "anon");
//        是不是必须加上/代表是static 目录下面的
        filterMap.put("/index.html", "anon");
        //================================//
        filterMap.put("/webjars/**", "anon");
        filterMap.put("/druid/**", "anon");
        filterMap.put("/app/**", "anon");
        filterMap.put("/shrio/login", "anon");
        filterMap.put("/sys/login", "anon");
        filterMap.put("/sys/registByWeb", "anon");
        filterMap.put("/swagger/**", "anon");
        filterMap.put("/v2/api-docs", "anon");
        filterMap.put("/swagger-ui.html", "anon");
        filterMap.put("/swagger-resources/**", "anon");
        filterMap.put("/captcha.jpg", "anon");
        filterMap.put("/aaa.txt", "anon");
//        filterMap.put("/**", "anon");
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);

        return shiroFilter;
    }

    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    
    
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    
    
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

}

The securityManager configuration uses a custom realm

4.2 OAuth2Realm

/**
 * 认证
 *
 * @author Mark [email protected]
 */
@Component
public class OAuth2Realm extends AuthorizingRealm {
    
    
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public boolean supports(AuthenticationToken token) {
    
    
        return token instanceof OAuth2Token;
    }
    /**
     * 授权(验证权限时调用)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    
    
        SysUserEntity user = (SysUserEntity)principals.getPrimaryPrincipal();
        Set<String> permsSet = user.getPermissions();
        //用户权限列表
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    }



    /**
     * 认证(登录时调用)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
        String accessToken = (String) token.getPrincipal();
        Long userId;
        try {
    
    
            Claims claims = JwtTokenUtil.parseJWT(accessToken);
            userId =  Long.valueOf(claims.get("userid").toString());
            Gson gson = new Gson();
            String  permsSetString = ( String )redisTemplate.opsForValue().get(userId.toString());
            Set<String> permsSet=  gson.fromJson(permsSetString,Set.class);
            SysUserEntity user = new SysUserEntity();
            user.setUserId(userId);
            user.setPermissions(permsSet);
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName());
            return info;
        } catch (Exception e) {
    
    
            e.printStackTrace();

            throw new IncorrectCredentialsException("token失效,请重新登录");

        }
    }
}

There is authentication and authorization logic in this realm. Authentication is to issue token first. Use jwt to verify the authenticity of the token. Then store the permission information in AuthenticationInfo. Then remove it in the authorization method.
This user class also adds a Permission field for storage.

	private Set<String> permissions;

4.4 The rest is some other configuration

For example, the configuration of redis

/**
 * Redis配置
 *
 * @author Mark [email protected]
 */
@Configuration
public class RedisConfig {
    
    
    @Autowired
    private RedisConnectionFactory factory;

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
    
    
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }

    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
    
    
        return redisTemplate.opsForHash();
    }

    @Bean
    public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
    
    
        return redisTemplate.opsForValue();
    }

    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
    
    
        return redisTemplate.opsForList();
    }

    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
    
    
        return redisTemplate.opsForSet();
    }

    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
    
    
        return redisTemplate.opsForZSet();
    }
}

Define shiro's filter

public class OAuth2Filter extends AuthenticatingFilter {
    
    

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
    
    
        //获取请求token
        String token = getRequestToken((HttpServletRequest) request);

        if(StringUtils.isBlank(token)){
    
    
            return null;
        }

        return new OAuth2Token(token);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    
    
        if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){
    
    
            return true;
        }

        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    
    
        //获取请求token,如果token不存在,直接返回401
        String token = getRequestToken((HttpServletRequest) request);
        if(StringUtils.isBlank(token)){
    
    
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());

            String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));

            httpResponse.getWriter().print(json);

            return false;
        }

        return executeLogin(request, response);
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
    
    
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
        try {
    
    
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());

            String json = new Gson().toJson(r);
            httpResponse.getWriter().print(json);
        } catch (IOException e1) {
    
    

        }

        return false;
    }

    /**
     * 获取请求的token
     */
    private String getRequestToken(HttpServletRequest httpRequest){
    
    
        //从header中获取token
        String token = httpRequest.getHeader("token");

        //如果header中不存在token,则从参数中获取token
        if(StringUtils.isBlank(token)){
    
    
            token = httpRequest.getParameter("token");
        }

        return token;
    }


}

4.5 There is also the most important need to make other modules importable.

insert image description here
Configure it like this in META-INFO.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.rose.permission.config.ShiroConfig,\
  com.rose.permission.oauth2.OAuth2Realm,\
  com.rose.permission.config.RedisConfig

Guess you like

Origin blog.csdn.net/qq_21561833/article/details/127605241