1.分散許可技術の選択
springSecurity についてはJWT + Shiro + Redis を使用して、
多くの配布を行うことができます。ただ、この企画の最初に使ったshiroと、権限に応じた役割やメニューが書かれているので、本体はshiroを使っています。jwt を使用して、各ユーザーの ID を識別します。redis を使用して、各ユーザーのアクセス許可を保存します。次に、毎回 redis からアクセス許可を読み取ります。
2. jwt を理解する
JWT は、ヘッダー (ヘッダー)、ペイロード (ペイロード)、署名 (署名) の 3 つの部分で構成されます。送信中、JWT の 3 つの部分が Base64 でエンコードされ、. で連結されて、最終的な送信文字列が形成されます。
ペイロード セクションにユーザーの ID を入力します。ユーザーの ID は、jwt 文字列から解析できます。
3. 現在のログインロジック
/**
* 登录
*/
@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);
}
トークンを生成し、ユーザー ID を保存し、アクセス許可を redis にロックします
4.shiroさんが作った設定
1.シロコンフィグ
*/
@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;
}
}
securityManager 構成はカスタム レルムを使用します
4.2 OAuth2レルム
/**
* 认证
*
* @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失效,请重新登录");
}
}
}
このレルムには、認証と承認のロジックがあります。認証は最初にトークンを発行することです。jwt を使用して、トークンの信頼性を検証します。次に、権限情報を AuthenticationInfo に格納します。次に、認証方法でそれを削除します。
このユーザー クラスは、ストレージ用のアクセス許可フィールドも追加します。
private Set<String> permissions;
4.4 残りは他の設定です
たとえば、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();
}
}
shiro のフィルターを定義する
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 他のモジュールをインポート可能にするという最も重要な必要性もあります。
META-INFO でこのように設定します。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.rose.permission.config.ShiroConfig,\
com.rose.permission.oauth2.OAuth2Realm,\
com.rose.permission.config.RedisConfig