mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-06-05 12:23:29 +08:00
#103 初步完成双重验证
This commit is contained in:
@@ -128,7 +128,6 @@ public interface Authentication extends Serializable {
|
||||
* @param <T> 属性值类型
|
||||
* @return Optional属性值
|
||||
*/
|
||||
@Deprecated
|
||||
<T extends Serializable> Optional<T> getAttribute(String name);
|
||||
|
||||
/**
|
||||
@@ -139,7 +138,6 @@ public interface Authentication extends Serializable {
|
||||
* @param object 属性值
|
||||
* @see AuthenticationManager#sync(Authentication)
|
||||
*/
|
||||
@Deprecated
|
||||
void setAttribute(String name, Serializable object);
|
||||
|
||||
/**
|
||||
@@ -148,7 +146,6 @@ public interface Authentication extends Serializable {
|
||||
* @param attributes 属性值map
|
||||
* @see AuthenticationManager#sync(Authentication)
|
||||
*/
|
||||
@Deprecated
|
||||
void setAttributes(Map<String, Serializable> attributes);
|
||||
|
||||
/**
|
||||
@@ -159,7 +156,6 @@ public interface Authentication extends Serializable {
|
||||
* @return 被删除的值
|
||||
* @see AuthenticationManager#sync(Authentication)
|
||||
*/
|
||||
@Deprecated
|
||||
<T extends Serializable> T removeAttributes(String name);
|
||||
|
||||
/**
|
||||
@@ -167,7 +163,6 @@ public interface Authentication extends Serializable {
|
||||
*
|
||||
* @return 全部属性集合
|
||||
*/
|
||||
@Deprecated
|
||||
Map<String, Serializable> getAttributes();
|
||||
|
||||
}
|
||||
|
||||
@@ -107,10 +107,5 @@ public @interface Authorize {
|
||||
*/
|
||||
RequiresDataAccess dataAccess() default @RequiresDataAccess(ignore = true);
|
||||
|
||||
/**
|
||||
* @return 双重验证
|
||||
*/
|
||||
TwoFactor twoFactor() default @TwoFactor(ignore = true);
|
||||
|
||||
String[] description() default {};
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ import java.lang.annotation.*;
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface TwoFactor {
|
||||
String operation() default "";
|
||||
String value();
|
||||
|
||||
long timeout() default 10 * 60 * 1000L;
|
||||
|
||||
String provider() default "totp";
|
||||
|
||||
String parameter() default "verifyCode";
|
||||
|
||||
boolean ignore() default false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.hswebframework.web.authorization.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 权限验证异常
|
||||
*
|
||||
@@ -10,6 +12,9 @@ public class AccessDenyException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -5135300127303801430L;
|
||||
|
||||
@Getter
|
||||
private String code;
|
||||
|
||||
public AccessDenyException() {
|
||||
this("权限不足,拒绝访问!");
|
||||
}
|
||||
@@ -21,4 +26,9 @@ public class AccessDenyException extends RuntimeException {
|
||||
public AccessDenyException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AccessDenyException(String message, String code, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.hswebframework.web.authorization.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@Getter
|
||||
public class NeedTwoFactorException extends RuntimeException {
|
||||
private static final long serialVersionUID = 3655980280834947633L;
|
||||
private String provider;
|
||||
|
||||
public NeedTwoFactorException(String message, String provider) {
|
||||
super(message);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.hswebframework.web.authorization.setting;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class SettingNullValueHolder implements SettingValueHolder {
|
||||
|
||||
public static final SettingNullValueHolder INSTANCE = new SettingNullValueHolder();
|
||||
|
||||
private SettingNullValueHolder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<List<T>> asList(Class<T> t) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> as(Class<T> t) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> asString() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> asLong() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Integer> asInt() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Double> asDouble() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Object> getValue() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSettingPermission getPermission() {
|
||||
return UserSettingPermission.NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.hswebframework.web.authorization.setting;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SettingValueHolder {
|
||||
|
||||
SettingValueHolder NULL = SettingNullValueHolder.INSTANCE;
|
||||
|
||||
<T> Optional<List<T>> asList(Class<T> t);
|
||||
|
||||
<T> Optional<T> as(Class<T> t);
|
||||
|
||||
Optional<String> asString();
|
||||
|
||||
Optional<Long> asLong();
|
||||
|
||||
Optional<Integer> asInt();
|
||||
|
||||
Optional<Double> asDouble();
|
||||
|
||||
Optional<Object> getValue();
|
||||
|
||||
UserSettingPermission getPermission();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package org.hswebframework.web.authorization.setting;
|
||||
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.utils.StringUtils;
|
||||
import org.hswebframework.web.dict.EnumDict;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class StringSourceSettingHolder implements SettingValueHolder {
|
||||
|
||||
private String value;
|
||||
|
||||
private UserSettingPermission permission;
|
||||
|
||||
public static SettingValueHolder of(String value, UserSettingPermission permission) {
|
||||
if (value == null) {
|
||||
return SettingValueHolder.NULL;
|
||||
}
|
||||
return new StringSourceSettingHolder(value, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<List<T>> asList(Class<T> t) {
|
||||
return getNativeValue()
|
||||
.map(v -> JSON.parseArray(v, t));
|
||||
}
|
||||
|
||||
protected <T> T convert(String value, Class<T> t) {
|
||||
if (t.isEnum()) {
|
||||
if (EnumDict.class.isAssignableFrom(t)) {
|
||||
T val = (T) EnumDict.find((Class) t, value).orElse(null);
|
||||
if (null != val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
for (T enumConstant : t.getEnumConstants()) {
|
||||
if (((Enum) enumConstant).name().equalsIgnoreCase(value)) {
|
||||
return enumConstant;
|
||||
}
|
||||
}
|
||||
}
|
||||
return JSON.parseObject(value, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public <T> Optional<T> as(Class<T> t) {
|
||||
if (t == String.class) {
|
||||
return (Optional) asString();
|
||||
} else if (Long.class == t || long.class == t) {
|
||||
return (Optional) asLong();
|
||||
} else if (Integer.class == t || int.class == t) {
|
||||
return (Optional) asInt();
|
||||
} else if (Double.class == t || double.class == t) {
|
||||
return (Optional) asDouble();
|
||||
}
|
||||
return getNativeValue().map(v -> convert(v, t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> asString() {
|
||||
return getNativeValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> asLong() {
|
||||
return getNativeValue().map(StringUtils::toLong);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Integer> asInt() {
|
||||
return getNativeValue().map(StringUtils::toInt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Double> asDouble() {
|
||||
return getNativeValue().map(StringUtils::toDouble);
|
||||
}
|
||||
|
||||
private Optional<String> getNativeValue() {
|
||||
return Optional.ofNullable(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Object> getValue() {
|
||||
return Optional.ofNullable(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.hswebframework.web.authorization.setting;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public interface UserSettingManager {
|
||||
|
||||
SettingValueHolder getSetting(String userId, String key);
|
||||
|
||||
void saveSetting(String userId, String key, String value, UserSettingPermission permission);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.hswebframework.web.authorization.setting;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.web.dict.Dict;
|
||||
import org.hswebframework.web.dict.EnumDict;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Dict(id = "user-setting-permission")
|
||||
public enum UserSettingPermission implements EnumDict<String> {
|
||||
NONE("无"),
|
||||
R("读"),
|
||||
W("写"),
|
||||
RW("读写");
|
||||
private String text;
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfi
|
||||
import org.hswebframework.web.authorization.token.DefaultUserTokenManager;
|
||||
import org.hswebframework.web.authorization.token.UserTokenAuthenticationSupplier;
|
||||
import org.hswebframework.web.authorization.token.UserTokenManager;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
|
||||
import org.hswebframework.web.authorization.twofactor.defaults.DefaultTwoFactorValidatorManager;
|
||||
import org.hswebframework.web.convert.CustomMessageConverter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
@@ -56,6 +58,12 @@ public class DefaultAuthorizationAutoConfiguration {
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TwoFactorValidatorManager.class)
|
||||
public DefaultTwoFactorValidatorManager defaultTwoFactorValidatorManager() {
|
||||
return new DefaultTwoFactorValidatorManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AuthenticationBuilderFactory.class)
|
||||
public AuthenticationBuilderFactory authenticationBuilderFactory(DataAccessConfigBuilderFactory dataAccessConfigBuilderFactory) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.hswebframework.web.authorization.twofactor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public interface TwoFactorToken extends Serializable {
|
||||
void generate(long timeout);
|
||||
|
||||
boolean expired();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.hswebframework.web.authorization.twofactor;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public interface TwoFactorTokenManager {
|
||||
TwoFactorToken getToken(String userId, String operation);
|
||||
}
|
||||
@@ -2,9 +2,14 @@ package org.hswebframework.web.authorization.twofactor;
|
||||
|
||||
/**
|
||||
* 双重验证器,用于某些接口需要双重验证时使用,如: 短信验证码,动态口令等
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public interface TwoFactorValidator {
|
||||
|
||||
String getProvider();
|
||||
|
||||
/**
|
||||
* 验证code是否有效,如果验证码有效,则保持此验证有效期.在有效期内,调用{@link this#expired()} 将返回false
|
||||
*
|
||||
@@ -20,6 +25,4 @@ public interface TwoFactorValidator {
|
||||
* @return 是否过期
|
||||
*/
|
||||
boolean expired();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package org.hswebframework.web.authorization.twofactor;
|
||||
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
|
||||
/**
|
||||
* 双重验证管理器
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public interface TwoFactorValidatorManager {
|
||||
|
||||
/**
|
||||
* 获取用户使用的双重验证器
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @param operation 进行的操作
|
||||
* @param provider 验证器供应商
|
||||
* @return 验证器
|
||||
*/
|
||||
TwoFactorValidator getValidator(String userId, String operation);
|
||||
TwoFactorValidator getValidator(String userId,String operation, String provider);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.hswebframework.web.authorization.twofactor;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public interface TwoFactorValidatorProvider {
|
||||
|
||||
String getProvider();
|
||||
|
||||
TwoFactorValidator createTwoFactorValidator(String userId,String operation);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.hswebframework.web.authorization.twofactor.defaults;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorToken;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class DefaultTwoFactorValidator implements TwoFactorValidator {
|
||||
|
||||
@Getter
|
||||
private String provider;
|
||||
|
||||
private Function<String, Boolean> validator;
|
||||
|
||||
private Supplier<TwoFactorToken> tokenSupplier;
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, long timeout) {
|
||||
boolean success = validator.apply(code);
|
||||
if (success) {
|
||||
tokenSupplier.get().generate(timeout);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expired() {
|
||||
return tokenSupplier.get().expired();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.hswebframework.web.authorization.twofactor.defaults;
|
||||
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public class DefaultTwoFactorValidatorManager implements TwoFactorValidatorManager, BeanPostProcessor {
|
||||
|
||||
private String defaultProvider = "totp";
|
||||
|
||||
private Map<String, TwoFactorValidatorProvider> providers = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public TwoFactorValidator getValidator(String userId, String operation, String provider) {
|
||||
if (provider == null) {
|
||||
provider = defaultProvider;
|
||||
}
|
||||
TwoFactorValidatorProvider validatorProvider = providers.get(provider);
|
||||
if (validatorProvider == null) {
|
||||
return new UnsupportedTwoFactorValidator(provider);
|
||||
}
|
||||
return validatorProvider.createTwoFactorValidator(userId, operation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof TwoFactorValidatorProvider) {
|
||||
TwoFactorValidatorProvider provider = ((TwoFactorValidatorProvider) bean);
|
||||
providers.put(provider.getProvider(), provider);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.hswebframework.web.authorization.twofactor.defaults;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorTokenManager;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@Getter
|
||||
public abstract class DefaultTwoFactorValidatorProvider implements TwoFactorValidatorProvider {
|
||||
|
||||
private String provider;
|
||||
|
||||
private TwoFactorTokenManager twoFactorTokenManager;
|
||||
|
||||
public DefaultTwoFactorValidatorProvider(String provider, TwoFactorTokenManager twoFactorTokenManager) {
|
||||
this.provider = provider;
|
||||
this.twoFactorTokenManager = twoFactorTokenManager;
|
||||
}
|
||||
|
||||
protected abstract boolean validate(String userId, String code);
|
||||
|
||||
@Override
|
||||
public TwoFactorValidator createTwoFactorValidator(String userId, String operation) {
|
||||
return new DefaultTwoFactorValidator(getProvider(), code -> validate(userId, code), () -> twoFactorTokenManager.getToken(userId, operation));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.hswebframework.web.authorization.twofactor.defaults;
|
||||
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorToken;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorTokenManager;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public class HashMapTwoFactorTokenManager implements TwoFactorTokenManager {
|
||||
|
||||
private Map<String, WeakReference<TwoFactorTokenInfo>> tokens = new ConcurrentHashMap<>();
|
||||
|
||||
private class TwoFactorTokenInfo implements Serializable {
|
||||
private volatile long lastRequestTime = System.currentTimeMillis();
|
||||
|
||||
private long timeOut;
|
||||
|
||||
private boolean isExpire() {
|
||||
return System.currentTimeMillis() - lastRequestTime >= timeOut;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String createTokenInfoKey(String userId, String operation) {
|
||||
return userId + "_" + operation;
|
||||
}
|
||||
|
||||
private TwoFactorTokenInfo getTokenInfo(String userId, String operation) {
|
||||
return Optional.ofNullable(tokens.get(createTokenInfoKey(userId, operation)))
|
||||
.map(WeakReference::get)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TwoFactorToken getToken(String userId, String operation) {
|
||||
|
||||
return new TwoFactorToken() {
|
||||
private static final long serialVersionUID = -5148037320548431456L;
|
||||
|
||||
@Override
|
||||
public void generate(long timeout) {
|
||||
TwoFactorTokenInfo info = new TwoFactorTokenInfo();
|
||||
info.timeOut = timeout;
|
||||
tokens.put(createTokenInfoKey(userId, operation), new WeakReference<>(info));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expired() {
|
||||
TwoFactorTokenInfo info = getTokenInfo(userId, operation);
|
||||
if (info == null) {
|
||||
return true;
|
||||
}
|
||||
if (info.isExpire()) {
|
||||
tokens.remove(createTokenInfoKey(userId, operation));
|
||||
return true;
|
||||
}
|
||||
info.lastRequestTime = System.currentTimeMillis();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.hswebframework.web.authorization.twofactor.defaults;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class UnsupportedTwoFactorValidator implements TwoFactorValidator {
|
||||
|
||||
@Getter
|
||||
private String provider;
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, long timeout) {
|
||||
throw new UnsupportedOperationException("不支持的验证规则:" + provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expired() {
|
||||
throw new UnsupportedOperationException("不支持的验证规则:" + provider);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.hswebframework.web.authorization.twofactor.defaults;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorToken;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public class HashMapTwoFactorTokenManagerTest {
|
||||
|
||||
HashMapTwoFactorTokenManager tokenManager = new HashMapTwoFactorTokenManager();
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void test() {
|
||||
TwoFactorToken twoFactorToken = tokenManager.getToken("test", "test");
|
||||
|
||||
Assert.assertTrue(twoFactorToken.expired());
|
||||
twoFactorToken.generate(1000L);
|
||||
Assert.assertFalse(twoFactorToken.expired());
|
||||
Thread.sleep(1100);
|
||||
Assert.assertTrue(twoFactorToken.expired());
|
||||
}
|
||||
}
|
||||
@@ -31,4 +31,5 @@ public class AopAuthorizeAutoConfiguration {
|
||||
|
||||
return new AopAuthorizingController(authorizingHandler, aopMethodAuthorizeDefinitionParser);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,17 +8,19 @@ import org.hswebframework.web.authorization.basic.embed.EmbedAuthenticationManag
|
||||
import org.hswebframework.web.authorization.basic.handler.DefaultAuthorizingHandler;
|
||||
import org.hswebframework.web.authorization.basic.handler.UserAllowPermissionHandler;
|
||||
import org.hswebframework.web.authorization.basic.handler.access.DefaultDataAccessController;
|
||||
import org.hswebframework.web.authorization.basic.twofactor.TwoFactorHandlerInterceptorAdapter;
|
||||
import org.hswebframework.web.authorization.basic.web.*;
|
||||
import org.hswebframework.web.authorization.basic.web.session.UserTokenAutoExpiredListener;
|
||||
import org.hswebframework.web.authorization.token.UserTokenManager;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.*;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
@@ -58,6 +60,20 @@ public class AuthorizingHandlerAutoConfiguration {
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "hsweb.authorize.two-factor", name = "enable", havingValue = "true")
|
||||
@Order(100)
|
||||
public WebMvcConfigurer twoFactorHandlerConfigurer(TwoFactorValidatorManager manager) {
|
||||
return new WebMvcConfigurerAdapter() {
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new TwoFactorHandlerInterceptorAdapter(manager));
|
||||
super.addInterceptors(registry);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public WebMvcConfigurer webUserTokenInterceptorConfigurer(UserTokenManager userTokenManager,
|
||||
AopMethodAuthorizeDefinitionParser parser,
|
||||
List<UserTokenParser> userTokenParser) {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.hswebframework.web.authorization.basic.twofactor;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.authorization.User;
|
||||
import org.hswebframework.web.authorization.annotation.TwoFactor;
|
||||
import org.hswebframework.web.authorization.exception.NeedTwoFactorException;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class TwoFactorHandlerInterceptorAdapter extends HandlerInterceptorAdapter {
|
||||
|
||||
private TwoFactorValidatorManager validatorManager;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
if (handler instanceof HandlerMethod) {
|
||||
HandlerMethod method = ((HandlerMethod) handler);
|
||||
TwoFactor factor = method.getMethodAnnotation(TwoFactor.class);
|
||||
if (factor == null || factor.ignore()) {
|
||||
return true;
|
||||
}
|
||||
String userId = Authentication.current()
|
||||
.map(Authentication::getUser)
|
||||
.map(User::getId)
|
||||
.orElse(null);
|
||||
TwoFactorValidator validator = validatorManager.getValidator(userId, factor.value(), factor.provider());
|
||||
if (!validator.expired()) {
|
||||
return true;
|
||||
}
|
||||
String code = request.getParameter(factor.parameter());
|
||||
if (StringUtils.isEmpty(code)) {
|
||||
throw new NeedTwoFactorException("需要进行双重验证", factor.provider());
|
||||
} else if (!validator.verify(code, factor.timeout())) {
|
||||
throw new NeedTwoFactorException("验证码错误", factor.provider());
|
||||
}
|
||||
}
|
||||
return super.preHandle(request, response, handler);
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,29 @@ class FullFunctionTest extends Specification {
|
||||
}
|
||||
|
||||
|
||||
def "测试双重验证"() {
|
||||
given: "登录"
|
||||
def token = doLogin("admin", "admin")
|
||||
when: "登录成功"
|
||||
token != null
|
||||
then: "调用双重验证接口"
|
||||
mockMvc.perform(get("/test/two-factor")
|
||||
.header("token", token))
|
||||
.andExpect(status().is(403))
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString()
|
||||
def resp = mockMvc.perform(get("/test/two-factor")
|
||||
.header("token", token)
|
||||
.param("verifyCode", "test"))
|
||||
.andExpect(status().is(200))
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString()
|
||||
expect:
|
||||
resp != null
|
||||
}
|
||||
|
||||
def "测试查询"() {
|
||||
given: "登录"
|
||||
def token = doLogin("admin", "admin")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.hswebframework.web.authorization.full.controller;
|
||||
|
||||
import org.hswebframework.web.authorization.annotation.Authorize;
|
||||
import org.hswebframework.web.authorization.annotation.TwoFactor;
|
||||
import org.hswebframework.web.authorization.full.controller.model.TestModel;
|
||||
import org.hswebframework.web.controller.message.ResponseMessage;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -21,4 +23,10 @@ public class TestCrudController implements CrudController<TestModel> {
|
||||
|
||||
return ResponseMessage.ok();
|
||||
}
|
||||
|
||||
@TwoFactor(value = "test", provider = "test")
|
||||
@GetMapping("/two-factor")
|
||||
public ResponseMessage<String> testTowFactor() {
|
||||
return ResponseMessage.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.hswebframework.web.authorization.full.controller;
|
||||
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@Component
|
||||
public class TestTwoFactorValidatorProvider implements TwoFactorValidatorProvider {
|
||||
@Override
|
||||
public String getProvider() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TwoFactorValidator createTwoFactorValidator(String userId, String operation) {
|
||||
return new TwoFactorValidator() {
|
||||
boolean success = false;
|
||||
|
||||
@Override
|
||||
public String getProvider() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, long timeout) {
|
||||
return success = code.equalsIgnoreCase("test");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expired() {
|
||||
return !success;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,60 @@
|
||||
|
||||
spring:
|
||||
aop:
|
||||
auto: true
|
||||
proxy-target-class: true
|
||||
datasource:
|
||||
url : jdbc:h2:mem:example-oauth2-client
|
||||
username : sa
|
||||
password :
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name : org.h2.Driver
|
||||
cache:
|
||||
type: simple
|
||||
aop:
|
||||
auto: true
|
||||
proxy-target-class: true
|
||||
datasource:
|
||||
url: jdbc:h2:mem:example-oauth2-client
|
||||
username: sa
|
||||
password:
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name: org.h2.Driver
|
||||
cache:
|
||||
type: simple
|
||||
hsweb:
|
||||
app:
|
||||
name: hsweb-oauth2 客户端示例
|
||||
version: 3.0.0
|
||||
authorize:
|
||||
allows:
|
||||
users:
|
||||
admin: "**.TestController.*"
|
||||
users:
|
||||
admin:
|
||||
name: 超级管理员
|
||||
username: admin
|
||||
password: admin
|
||||
roles: #用户的角色
|
||||
- id: admin
|
||||
name: 管理员
|
||||
- id: user
|
||||
name: 用户
|
||||
permissions-simple:
|
||||
test: query,get
|
||||
permissions:
|
||||
- id: user-manager
|
||||
actions: query,get,update,delete
|
||||
dataAccesses:
|
||||
- action: query
|
||||
type: DENY_FIELDS
|
||||
fields:
|
||||
- password
|
||||
- salt
|
||||
- id: test
|
||||
actions: query,add,update
|
||||
dataAccesses:
|
||||
- action: query
|
||||
type: DENY_FIELDS
|
||||
fields:
|
||||
- password
|
||||
- action: update
|
||||
type: DENY_FIELDS
|
||||
fields:
|
||||
- name
|
||||
- action: add
|
||||
type: DENY_FIELDS
|
||||
fields:
|
||||
- id
|
||||
app:
|
||||
name: hsweb-oauth2 客户端示例
|
||||
version: 3.0.0
|
||||
authorize:
|
||||
allows:
|
||||
users:
|
||||
admin: "**.TestController.*"
|
||||
two-factor:
|
||||
enable: true
|
||||
users:
|
||||
admin:
|
||||
name: 超级管理员
|
||||
username: admin
|
||||
password: admin
|
||||
roles: #用户的角色
|
||||
- id: admin
|
||||
name: 管理员
|
||||
- id: user
|
||||
name: 用户
|
||||
permissions-simple:
|
||||
test: query,get
|
||||
permissions:
|
||||
- id: user-manager
|
||||
actions: query,get,update,delete
|
||||
dataAccesses:
|
||||
- action: query
|
||||
type: DENY_FIELDS
|
||||
fields:
|
||||
- password
|
||||
- salt
|
||||
- id: test
|
||||
actions: query,add,update
|
||||
dataAccesses:
|
||||
- action: query
|
||||
type: DENY_FIELDS
|
||||
fields:
|
||||
- password
|
||||
- action: update
|
||||
type: DENY_FIELDS
|
||||
fields:
|
||||
- name
|
||||
- action: add
|
||||
type: DENY_FIELDS
|
||||
fields:
|
||||
- id
|
||||
server:
|
||||
port: 8808
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.alibaba.fastjson.JSONException;
|
||||
import org.hswebframework.web.BusinessException;
|
||||
import org.hswebframework.web.NotFoundException;
|
||||
import org.hswebframework.web.authorization.exception.AccessDenyException;
|
||||
import org.hswebframework.web.authorization.exception.NeedTwoFactorException;
|
||||
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
|
||||
import org.hswebframework.web.controller.message.ResponseMessage;
|
||||
import org.hswebframework.web.validate.SimpleValidateResults;
|
||||
@@ -196,6 +197,15 @@ public class RestControllerExceptionTranslator {
|
||||
return ResponseMessage.error(400, msg);
|
||||
}
|
||||
|
||||
@ExceptionHandler(NeedTwoFactorException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
ResponseMessage handleException(NeedTwoFactorException e) {
|
||||
return ResponseMessage
|
||||
.error(403, e.getMessage())
|
||||
.code("need_tow_factor")
|
||||
.result(e.getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求方式不支持异常
|
||||
* 比如:POST方式的API, GET方式请求
|
||||
|
||||
@@ -4,6 +4,7 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.validator.constraints.NotBlank;
|
||||
import org.hswebframework.web.authorization.setting.UserSettingPermission;
|
||||
import org.hswebframework.web.commons.entity.SimpleGenericEntity;
|
||||
import org.hswebframework.web.validator.group.CreateGroup;
|
||||
|
||||
@@ -21,7 +22,6 @@ public class UserSettingEntity extends SimpleGenericEntity<String> {
|
||||
private String userId;
|
||||
|
||||
@NotBlank(groups = CreateGroup.class)
|
||||
|
||||
private String key;
|
||||
|
||||
@NotBlank(groups = CreateGroup.class)
|
||||
@@ -38,4 +38,17 @@ public class UserSettingEntity extends SimpleGenericEntity<String> {
|
||||
|
||||
private Date updateTime;
|
||||
|
||||
private UserSettingPermission permission;
|
||||
|
||||
public boolean hasPermission(UserSettingPermission... permissions) {
|
||||
if (permission == null) {
|
||||
return true;
|
||||
}
|
||||
if (permission == UserSettingPermission.NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return permission.in(permissions);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,17 @@ public interface UserService extends
|
||||
QueryService<UserEntity, String>,
|
||||
InsertService<UserEntity, String> {
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
*
|
||||
* @param data 要添加的数据
|
||||
* @return 用户id
|
||||
* @see org.hswebframework.web.service.authorization.events.UserCreatedEvent
|
||||
* @see BindRoleUserEntity
|
||||
*/
|
||||
@Override
|
||||
String insert(UserEntity data);
|
||||
|
||||
/**
|
||||
* 启用用户
|
||||
*
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.hswebframework.web.service.authorization.events;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.entity.authorization.UserEntity;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class TotpTwoFactorCreatedEvent {
|
||||
private UserEntity userEntity;
|
||||
|
||||
private String totpUrl;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.hswebframework.web.service.authorization.events;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.entity.authorization.UserEntity;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class UserCreatedEvent {
|
||||
UserEntity userEntity;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.hswebframework.web.service.authorization.simple;
|
||||
|
||||
import org.hswebframework.web.authorization.setting.SettingValueHolder;
|
||||
import org.hswebframework.web.authorization.setting.StringSourceSettingHolder;
|
||||
import org.hswebframework.web.authorization.setting.UserSettingManager;
|
||||
import org.hswebframework.web.authorization.setting.UserSettingPermission;
|
||||
import org.hswebframework.web.entity.authorization.UserSettingEntity;
|
||||
import org.hswebframework.web.service.authorization.UserSettingService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@Service
|
||||
public class InServiceUserSettingManager implements UserSettingManager {
|
||||
|
||||
@Autowired
|
||||
private UserSettingService userSettingService;
|
||||
|
||||
@Override
|
||||
public SettingValueHolder getSetting(String userId, String key) {
|
||||
UserSettingEntity entity = userSettingService.selectByUser(userId, "user-setting", key);
|
||||
if (entity == null) {
|
||||
return SettingValueHolder.NULL;
|
||||
}
|
||||
return StringSourceSettingHolder.of(entity.getSetting(), entity.getPermission());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveSetting(String userId, String key, String value, UserSettingPermission permission) {
|
||||
UserSettingEntity entity = new UserSettingEntity();
|
||||
entity.setUserId(userId);
|
||||
entity.setKey("user-setting");
|
||||
entity.setSettingId(key);
|
||||
entity.setSetting(value);
|
||||
entity.setPermission(permission);
|
||||
userSettingService.saveOrUpdate(entity);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import org.hswebframework.web.id.IDGenerator;
|
||||
import org.hswebframework.web.service.AbstractService;
|
||||
import org.hswebframework.web.service.DefaultDSLQueryService;
|
||||
import org.hswebframework.web.service.authorization.events.ClearUserAuthorizationCacheEvent;
|
||||
import org.hswebframework.web.service.authorization.events.UserCreatedEvent;
|
||||
import org.hswebframework.web.service.authorization.events.UserModifiedEvent;
|
||||
import org.hswebframework.web.service.authorization.simple.terms.UserInRoleSqlTerm;
|
||||
import org.hswebframework.web.validate.ValidationException;
|
||||
@@ -156,6 +157,7 @@ public class SimpleUserService extends AbstractService<UserEntity, String>
|
||||
trySyncUserRole(userEntity.getId(), bindRoleUserEntity.getRoles());
|
||||
}
|
||||
}
|
||||
publisher.publishEvent(new UserCreatedEvent(userEntity));
|
||||
return userEntity.getId();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,20 @@ public class SimpleUserSettingService extends EnableCacheGenericEntityService<Us
|
||||
return userSettingDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean dataExisted(UserSettingEntity entity) {
|
||||
UserSettingEntity old = createQuery()
|
||||
.where(entity::getUserId)
|
||||
.and(entity::getKey)
|
||||
.and(entity::getSettingId)
|
||||
.single();
|
||||
if (old != null) {
|
||||
entity.setId(old.getId());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(key = "'user:'+#userId+'.'+#key")
|
||||
public List<UserSettingEntity> selectByUser(String userId, String key) {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package org.hswebframework.web.service.authorization.simple.totp; /**
|
||||
*/
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Encodes arbitrary byte arrays as case-insensitive base-32 strings.
|
||||
* <p>
|
||||
* The implementation is slightly different than in RFC 4648. During encoding,
|
||||
* padding is not added, and during decoding the last incomplete chunk is not
|
||||
* taken into account. The result is that multiple strings decode to the same
|
||||
* byte array, for example, string of sixteen 7s ("7...7") and seventeen 7s both
|
||||
* decode to the same byte array.
|
||||
* TODO(sarvar): Revisit this encoding and whether this ambiguity needs fixing.
|
||||
*
|
||||
* @author sweis@google.com (Steve Weis)
|
||||
* @author Neal Gafter
|
||||
*/
|
||||
public class Base32String {
|
||||
// singleton
|
||||
|
||||
private static final Base32String INSTANCE =
|
||||
new Base32String("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"); // RFC 4648/3548
|
||||
|
||||
static Base32String getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
// 32 alpha-numeric characters.
|
||||
private String ALPHABET;
|
||||
private char[] DIGITS;
|
||||
private int MASK;
|
||||
private int SHIFT;
|
||||
private HashMap<Character, Integer> CHAR_MAP;
|
||||
|
||||
static final String SEPARATOR = "-";
|
||||
|
||||
protected Base32String(String alphabet) {
|
||||
this.ALPHABET = alphabet;
|
||||
DIGITS = ALPHABET.toCharArray();
|
||||
MASK = DIGITS.length - 1;
|
||||
SHIFT = Integer.numberOfTrailingZeros(DIGITS.length);
|
||||
CHAR_MAP = new HashMap<>();
|
||||
for (int i = 0; i < DIGITS.length; i++) {
|
||||
CHAR_MAP.put(DIGITS[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] decode(String encoded) throws DecodingException {
|
||||
return getInstance().decodeInternal(encoded);
|
||||
}
|
||||
|
||||
protected byte[] decodeInternal(String encoded) throws DecodingException {
|
||||
// Remove whitespace and separators
|
||||
encoded = encoded.trim().replaceAll(SEPARATOR, "").replaceAll(" ", "");
|
||||
|
||||
// Remove padding. Note: the padding is used as hint to determine how many
|
||||
// bits to decode from the last incomplete chunk (which is commented out
|
||||
// below, so this may have been wrong to start with).
|
||||
encoded = encoded.replaceFirst("[=]*$", "");
|
||||
|
||||
// Canonicalize to all upper case
|
||||
encoded = encoded.toUpperCase(Locale.US);
|
||||
if (encoded.length() == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
int encodedLength = encoded.length();
|
||||
int outLength = encodedLength * SHIFT / 8;
|
||||
byte[] result = new byte[outLength];
|
||||
int buffer = 0;
|
||||
int next = 0;
|
||||
int bitsLeft = 0;
|
||||
for (char c : encoded.toCharArray()) {
|
||||
if (!CHAR_MAP.containsKey(c)) {
|
||||
throw new DecodingException("Illegal character: " + c);
|
||||
}
|
||||
buffer <<= SHIFT;
|
||||
buffer |= CHAR_MAP.get(c) & MASK;
|
||||
bitsLeft += SHIFT;
|
||||
if (bitsLeft >= 8) {
|
||||
result[next++] = (byte) (buffer >> (bitsLeft - 8));
|
||||
bitsLeft -= 8;
|
||||
}
|
||||
}
|
||||
// We'll ignore leftover bits for now.
|
||||
//
|
||||
// if (next != outLength || bitsLeft >= SHIFT) {
|
||||
// throw new DecodingException("Bits left: " + bitsLeft);
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String encode(byte[] data) {
|
||||
return getInstance().encodeInternal(data);
|
||||
}
|
||||
|
||||
protected String encodeInternal(byte[] data) {
|
||||
if (data.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// SHIFT is the number of bits per output character, so the length of the
|
||||
// output is the length of the input multiplied by 8/SHIFT, rounded up.
|
||||
if (data.length >= (1 << 28)) {
|
||||
// The computation below will fail, so don't do it.
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
int outputLength = (data.length * 8 + SHIFT - 1) / SHIFT;
|
||||
StringBuilder result = new StringBuilder(outputLength);
|
||||
|
||||
int buffer = data[0];
|
||||
int next = 1;
|
||||
int bitsLeft = 8;
|
||||
while (bitsLeft > 0 || next < data.length) {
|
||||
if (bitsLeft < SHIFT) {
|
||||
if (next < data.length) {
|
||||
buffer <<= 8;
|
||||
buffer |= (data[next++] & 0xff);
|
||||
bitsLeft += 8;
|
||||
} else {
|
||||
int pad = SHIFT - bitsLeft;
|
||||
buffer <<= pad;
|
||||
bitsLeft += pad;
|
||||
}
|
||||
}
|
||||
int index = MASK & (buffer >> (bitsLeft - SHIFT));
|
||||
bitsLeft -= SHIFT;
|
||||
result.append(DIGITS[index]);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
// enforce that this class is a singleton
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
throw new CloneNotSupportedException();
|
||||
}
|
||||
|
||||
public static class DecodingException extends Exception {
|
||||
public DecodingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.hswebframework.web.service.authorization.simple.totp;
|
||||
|
||||
public class HexEncoding {
|
||||
|
||||
/** Hidden constructor to prevent instantiation. */
|
||||
private HexEncoding() {}
|
||||
|
||||
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
|
||||
|
||||
/**
|
||||
* Encodes the provided data as a hexadecimal string.
|
||||
*/
|
||||
public static String encode(byte[] data) {
|
||||
StringBuilder result = new StringBuilder(data.length * 2);
|
||||
for (byte b : data) {
|
||||
result.append(HEX_DIGITS[(b >>> 4) & 0x0f]);
|
||||
result.append(HEX_DIGITS[b & 0x0f]);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the provided hexadecimal string into an array of bytes.
|
||||
*/
|
||||
public static byte[] decode(String encoded) {
|
||||
// IMPLEMENTATION NOTE: Special care is taken to permit odd number of hexadecimal digits.
|
||||
int resultLengthBytes = (encoded.length() + 1) / 2;
|
||||
byte[] result = new byte[resultLengthBytes];
|
||||
int resultOffset = 0;
|
||||
int encodedCharOffset = 0;
|
||||
if ((encoded.length() % 2) != 0) {
|
||||
// Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
|
||||
result[resultOffset++] = (byte) getHexadecimalDigitValue(encoded.charAt(encodedCharOffset));
|
||||
encodedCharOffset++;
|
||||
}
|
||||
for (int len = encoded.length(); encodedCharOffset < len; encodedCharOffset += 2) {
|
||||
result[resultOffset++] = (byte)
|
||||
((getHexadecimalDigitValue(encoded.charAt(encodedCharOffset)) << 4)
|
||||
| getHexadecimalDigitValue(encoded.charAt(encodedCharOffset + 1)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int getHexadecimalDigitValue(char c) {
|
||||
if ((c >= 'a') && (c <= 'f')) {
|
||||
return (c - 'a') + 0x0a;
|
||||
} else if ((c >= 'A') && (c <= 'F')) {
|
||||
return (c - 'A') + 0x0a;
|
||||
} else if ((c >= '0') && (c <= '9')) {
|
||||
return c - '0';
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid hexadecimal digit at position : '" + c + "' (0x" + Integer.toHexString(c) + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.hswebframework.web.service.authorization.simple.totp;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.authorization.setting.UserSettingManager;
|
||||
import org.hswebframework.web.authorization.setting.UserSettingPermission;
|
||||
import org.hswebframework.web.authorization.twofactor.defaults.DefaultTwoFactorValidatorProvider;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorTokenManager;
|
||||
import org.hswebframework.web.entity.authorization.UserEntity;
|
||||
import org.hswebframework.web.service.authorization.events.TotpTwoFactorCreatedEvent;
|
||||
import org.hswebframework.web.service.authorization.events.UserCreatedEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class TotpTwoFactorProvider extends DefaultTwoFactorValidatorProvider {
|
||||
|
||||
private UserSettingManager userSettingManager;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String domain = "hsweb.me";
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String settingId = "tow-factor-totp-key";
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
public TotpTwoFactorProvider(UserSettingManager userSettingManager, TwoFactorTokenManager twoFactorTokenManager) {
|
||||
super("totp", twoFactorTokenManager);
|
||||
this.userSettingManager = userSettingManager;
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void handleUserCreatedEvent(UserCreatedEvent event) {
|
||||
//生成totp
|
||||
String key = TotpUtil.getRandomSecretBase32(64);
|
||||
UserEntity userEntity = event.getUserEntity();
|
||||
String keyUrl = TotpUtil.generateTotpString(userEntity.getUsername(), domain, key);
|
||||
//创建一个用户没有操作权限的配置
|
||||
userSettingManager.saveSetting(userEntity.getId(), settingId, key, UserSettingPermission.NONE);
|
||||
eventPublisher.publishEvent(new TotpTwoFactorCreatedEvent(userEntity, keyUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean validate(String userId, String code) {
|
||||
return userSettingManager.getSetting(userId, settingId)
|
||||
.asString()
|
||||
.map(key -> TotpUtil.verify(key, code))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package org.hswebframework.web.service.authorization.simple.totp;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class TotpUtil {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TotpUtil.class);
|
||||
|
||||
private TotpUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method uses the JCE to provide the crypto algorithm.
|
||||
* HMAC computes a Hashed Message Authentication Code with the
|
||||
* crypto hash algorithm as a parameter.
|
||||
*
|
||||
* @param crypto: the crypto algorithm (HmacSHA1, HmacSHA256,
|
||||
* HmacSHA512)
|
||||
* @param keyBytes: the bytes to use for the HMAC key
|
||||
* @param text: the message or text to be authenticated
|
||||
*/
|
||||
private static byte[] hmac_sha(String crypto, byte[] keyBytes,
|
||||
byte[] text) {
|
||||
try {
|
||||
Mac hmac;
|
||||
hmac = Mac.getInstance(crypto);
|
||||
SecretKeySpec macKey =
|
||||
new SecretKeySpec(keyBytes, "RAW");
|
||||
hmac.init(macKey);
|
||||
return hmac.doFinal(text);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new UndeclaredThrowableException(gse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts a HEX string to Byte[]
|
||||
*
|
||||
* @param hex: the HEX string
|
||||
* @return: a byte array
|
||||
*/
|
||||
private static byte[] hexStr2Bytes(String hex) {
|
||||
// Adding one byte to get the right conversion
|
||||
// Values starting with "0" can be converted
|
||||
byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
|
||||
|
||||
// Copy all the REAL bytes, not the "first"
|
||||
byte[] ret = new byte[bArray.length - 1];
|
||||
for (int i = 0; i < ret.length; i++)
|
||||
ret[i] = bArray[i + 1];
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static final int[] DIGITS_POWER
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
= {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
|
||||
|
||||
/**
|
||||
* This method generates a TOTP value for the given
|
||||
* set of parameters.
|
||||
*
|
||||
* @param key: the shared secret, HEX encoded
|
||||
* @param time: a value that reflects a time
|
||||
* @param returnDigits: number of digits to return
|
||||
* @return: a numeric String in base 10 that includes truncationDigits digits
|
||||
*/
|
||||
public static String generateTOTP(String key,
|
||||
String time,
|
||||
String returnDigits) {
|
||||
return generateTOTP(key, time, returnDigits, "HmacSHA1");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method generates a TOTP value for the given
|
||||
* set of parameters.
|
||||
*
|
||||
* @param key: the shared secret, HEX encoded
|
||||
* @param time: a value that reflects a time
|
||||
* @param returnDigits: number of digits to return
|
||||
* @return: a numeric String in base 10 that includes truncationDigits digits
|
||||
*/
|
||||
public static String generateTOTP256(String key,
|
||||
String time,
|
||||
String returnDigits) {
|
||||
return generateTOTP(key, time, returnDigits, "HmacSHA256");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method generates a TOTP value for the given
|
||||
* set of parameters.
|
||||
*
|
||||
* @param key: the shared secret, HEX encoded
|
||||
* @param time: a value that reflects a time
|
||||
* @param returnDigits: number of digits to return
|
||||
* @return: a numeric String in base 10 that includes truncationDigits digits
|
||||
*/
|
||||
public static String generateTOTP512(String key,
|
||||
String time,
|
||||
String returnDigits) {
|
||||
return generateTOTP(key, time, returnDigits, "HmacSHA512");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method generates a TOTP value for the given
|
||||
* set of parameters.
|
||||
*
|
||||
* @param key: the shared secret, HEX encoded
|
||||
* @param time: a value that reflects a time
|
||||
* @param returnDigits: number of digits to return
|
||||
* @param crypto: the crypto function to use
|
||||
* @return: a numeric String in base 10 that includes truncationDigits digits
|
||||
*/
|
||||
public static String generateTOTP(String key,
|
||||
String time,
|
||||
String returnDigits,
|
||||
String crypto) {
|
||||
int codeDigits = Integer.decode(returnDigits);
|
||||
StringBuilder result;
|
||||
|
||||
// Using the counter
|
||||
// First 8 bytes are for the movingFactor
|
||||
// Compliant with base RFC 4226 (HOTP)
|
||||
StringBuilder timeBuilder = new StringBuilder(time);
|
||||
while (timeBuilder.length() < 16)
|
||||
timeBuilder.insert(0, "0");
|
||||
time = timeBuilder.toString();
|
||||
|
||||
// Get the HEX in a Byte[]
|
||||
byte[] msg = hexStr2Bytes(time);
|
||||
byte[] k = hexStr2Bytes(key);
|
||||
|
||||
byte[] hash = hmac_sha(crypto, k, msg);
|
||||
|
||||
// put selected bytes into result int
|
||||
int offset = hash[hash.length - 1] & 0xf;
|
||||
|
||||
int binary =
|
||||
((hash[offset] & 0x7f) << 24) |
|
||||
((hash[offset + 1] & 0xff) << 16) |
|
||||
((hash[offset + 2] & 0xff) << 8) |
|
||||
(hash[offset + 3] & 0xff);
|
||||
|
||||
int otp = binary % DIGITS_POWER[codeDigits];
|
||||
|
||||
result = new StringBuilder(Integer.toString(otp));
|
||||
while (result.length() < codeDigits) {
|
||||
result.insert(0, "0");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证动态口令是否正确
|
||||
*
|
||||
* @param secretBase32 密钥
|
||||
* @param code 待验证的动态口令
|
||||
* @return
|
||||
*/
|
||||
public static boolean verify(String secretBase32, String code) {
|
||||
return generate(secretBase32).equals(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成totp协议字符串
|
||||
*
|
||||
* @param accoName
|
||||
* @param domain
|
||||
* @param secretBase32
|
||||
* @return
|
||||
*/
|
||||
public static String generateTotpString(String accoName, String domain, String secretBase32) {
|
||||
return "otpauth://totp/" + accoName + "@" + domain + "?secret=" + secretBase32;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据密钥生成动态口令
|
||||
*
|
||||
* @param secretBase32 base32编码格式的密钥
|
||||
* @return
|
||||
*/
|
||||
public static String generate(String secretBase32) {
|
||||
|
||||
String secretHex;
|
||||
try {
|
||||
secretHex = HexEncoding.encode(Base32String.decode(secretBase32));
|
||||
} catch (Base32String.DecodingException e) {
|
||||
LOGGER.error("解码" + secretBase32 + "出错,", e);
|
||||
throw new RuntimeException("解码Base32出错");
|
||||
}
|
||||
|
||||
long X = 30;
|
||||
|
||||
StringBuilder steps;
|
||||
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
df.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
long currentTime = System.currentTimeMillis() / 1000L;
|
||||
try {
|
||||
long t = currentTime / X;
|
||||
steps = new StringBuilder(Long.toHexString(t).toUpperCase());
|
||||
while (steps.length() < 16) steps.insert(0, "0");
|
||||
|
||||
return generateTOTP(secretHex, steps.toString(), "6",
|
||||
"HmacSHA1");
|
||||
} catch (final Exception e) {
|
||||
LOGGER.error("生成动态口令出错:" + secretBase32, e);
|
||||
throw new RuntimeException("生成动态口令出错");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成base32编码的随机密钥
|
||||
*
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
public static String getRandomSecretBase32(int length) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] salt = new byte[length / 2];
|
||||
random.nextBytes(salt);
|
||||
return Base32String.encode(salt);
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,16 @@ package org.hswebframework.web.authorization.starter;
|
||||
import org.hswebframework.web.authorization.AuthenticationInitializeService;
|
||||
import org.hswebframework.web.authorization.AuthenticationManager;
|
||||
import org.hswebframework.web.authorization.basic.embed.EmbedAuthenticationManager;
|
||||
import org.hswebframework.web.authorization.setting.UserSettingManager;
|
||||
import org.hswebframework.web.authorization.simple.DefaultAuthorizationAutoConfiguration;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorTokenManager;
|
||||
import org.hswebframework.web.authorization.twofactor.defaults.HashMapTwoFactorTokenManager;
|
||||
import org.hswebframework.web.service.authorization.simple.SimpleAuthenticationManager;
|
||||
import org.hswebframework.web.service.authorization.simple.totp.TotpTwoFactorProvider;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.*;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -78,4 +81,17 @@ public class AuthorizationAutoConfiguration {
|
||||
return new AutoSyncPermission();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TwoFactorTokenManager.class)
|
||||
public TwoFactorTokenManager twoFactorTokenManager() {
|
||||
return new HashMapTwoFactorTokenManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "hsweb.authorize.two-factor.totp", name = "enable", havingValue = "true")
|
||||
@ConfigurationProperties(prefix = "hsweb.authorize.two-factor.totp")
|
||||
public TotpTwoFactorProvider totpTwoFactorProvider(UserSettingManager userSettingManager,
|
||||
TwoFactorTokenManager twoFactorTokenManager) {
|
||||
return new TotpTwoFactorProvider(userSettingManager, twoFactorTokenManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,15 @@ var info = {
|
||||
|
||||
//版本更新信息
|
||||
var versions = [
|
||||
// {
|
||||
// version: "3.0.0",
|
||||
// upgrade: function (context) {
|
||||
// java.lang.System.out.println("更新到3.0.2了");
|
||||
// }
|
||||
// }
|
||||
{
|
||||
version: "3.0.4",
|
||||
upgrade: function (context) {
|
||||
var database = context.database;
|
||||
database.createOrAlter("s_user_setting")
|
||||
.addColumn().name("permission").varchar(32).comment("用户可操作权限").commit()
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
];
|
||||
var JDBCType = java.sql.JDBCType;
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.hswebframework.web.authorization.starter
|
||||
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager
|
||||
import org.hswebframework.web.entity.authorization.SimpleUserEntity
|
||||
import org.hswebframework.web.service.authorization.UserService
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0.4
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@SpringBootTest(classes = [TestApplication.class], properties = ["classpath:application.yml"])
|
||||
class TotpTwoFactorProviderTests extends Specification {
|
||||
|
||||
@Autowired
|
||||
TwoFactorValidatorManager validatorManager;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
|
||||
def "测试totp"() {
|
||||
given:
|
||||
String id = userService.insert(new SimpleUserEntity(
|
||||
username: "admin",
|
||||
password: "admin",
|
||||
name: "admin"
|
||||
))
|
||||
expect:
|
||||
!validatorManager.getValidator(id, "", "totp")
|
||||
.verify("test", 100)
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@ hsweb:
|
||||
authorize:
|
||||
sync: false
|
||||
auto-parse: false
|
||||
two-factor:
|
||||
totp:
|
||||
enable: true
|
||||
users:
|
||||
fix-bug-91-in-yml:
|
||||
username: "fix-bug-91-in-yml"
|
||||
|
||||
@@ -4,6 +4,8 @@ import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.authorization.annotation.Authorize;
|
||||
import org.hswebframework.web.authorization.exception.AccessDenyException;
|
||||
import org.hswebframework.web.authorization.setting.UserSettingPermission;
|
||||
import org.hswebframework.web.controller.message.ResponseMessage;
|
||||
import org.hswebframework.web.entity.authorization.UserSettingEntity;
|
||||
import org.hswebframework.web.service.authorization.UserSettingService;
|
||||
@@ -12,6 +14,9 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hswebframework.web.authorization.setting.UserSettingPermission.*;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
@@ -32,31 +37,44 @@ public class UserSettingController {
|
||||
public ResponseMessage<UserSettingEntity> get(Authentication authentication,
|
||||
@PathVariable String key,
|
||||
@PathVariable String id) {
|
||||
return ResponseMessage.ok(userSettingService.selectByUser(authentication.getUser().getId(), key, id));
|
||||
UserSettingEntity entity = userSettingService.selectByUser(authentication.getUser().getId(), key, id);
|
||||
if (entity != null && entity.hasPermission(R, RW)) {
|
||||
return ResponseMessage.ok();
|
||||
}
|
||||
return ResponseMessage.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/me/{key}")
|
||||
@Authorize(merge = false)
|
||||
@ApiOperation("获取当前用户的配置列表")
|
||||
public ResponseMessage<List<UserSettingEntity>> get(Authentication authentication,
|
||||
@PathVariable String key) {
|
||||
return ResponseMessage.ok(userSettingService.selectByUser(authentication.getUser().getId(), key));
|
||||
@PathVariable String key) {
|
||||
|
||||
return ResponseMessage.ok(userSettingService
|
||||
.selectByUser(authentication.getUser().getId(), key)
|
||||
.stream()
|
||||
.filter(setting -> setting.hasPermission(R, RW))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@PatchMapping("/me/{key}")
|
||||
@Authorize(merge = false)
|
||||
@ApiOperation("获取当前用户的配置列表")
|
||||
@ApiOperation("保存当前用户配置")
|
||||
public ResponseMessage<String> save(Authentication authentication,
|
||||
@PathVariable String key,
|
||||
@Validated
|
||||
@RequestBody UserSettingEntity userSettingEntity) {
|
||||
@PathVariable String key,
|
||||
@Validated
|
||||
@RequestBody UserSettingEntity userSettingEntity) {
|
||||
userSettingEntity.setId(null);
|
||||
userSettingEntity.setUserId(authentication.getUser().getId());
|
||||
userSettingEntity.setKey(key);
|
||||
UserSettingEntity old = userSettingService.selectByUser(authentication.getUser().getId(), key, userSettingEntity.getSettingId());
|
||||
if (old != null) {
|
||||
userSettingEntity.setId(old.getId());
|
||||
if (!old.hasPermission(RW, R)) {
|
||||
throw new AccessDenyException("没有权限保存此配置");
|
||||
}
|
||||
}
|
||||
userSettingEntity.setPermission(RW);
|
||||
String id = userSettingService.saveOrUpdate(userSettingEntity);
|
||||
return ResponseMessage.ok(id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user