js中,Tree树形控件数据由「数组格式」转换成「对象格式」

数组格式
数据库中通过pId维护关系

[{
    "id": 1576757423116692,
    "name": "\u57FA\u672C\u4FE1\u606F",
    "open": true,
    "icon": null,
    "iconSkin": null,
    "pId": null // 通过pId标示父节点
}, {
    "id": 1576830102194814,
    "name": "\u6D88\u606F\u67E5\u770B",
    "open": true,
    "icon": null,
    "iconSkin": null,
    "pId": 1576757423116692
}]

对象格式
Tree树形控件,通过children维护节点关系

[{
    "id": 1592539382001818,
    "name": "报表中心",
    "open": true,
    "icon": null,
    "iconSkin": null,
    "pId": null,
    "children": [{ // children嵌套
        "id": 1592539382041842,
        "name": "库存分析",
        "open": true,
        "icon": null,
        "iconSkin": null,
        "pId": 1592539382001818
    }]
}]

js中,Tree树形控件数据由「数组格式」转换成「对象格式」

 var permissionList = [{"id":1576757423116692,"name":"\u57FA\u672C\u4FE1\u606F","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576830102194814,"name":"\u6D88\u606F\u67E5\u770B","open":true,"icon":null,"iconSkin":null,"pId":1576830101976741},{"id":1576738932216633,"name":"\u7269\u6599\u54C1\u724C","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1576830009811502,"name":"\u6837\u54C1\u7533\u8BF7","open":true,"icon":null,"iconSkin":null,"pId":1576830009617716},{"id":1576830518754858,"name":"\u8BBF\u95EE\u65F6\u95F4\u63A7\u5236","open":true,"icon":null,"iconSkin":null,"pId":1576830518507293},{"id":1576757422730815,"name":"\u7269\u6599\u4FE1\u606F","open":true,"icon":null,"iconSkin":null,"pId":null},{"id":1576900467172242,"name":"DICETYPE","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576830331048506,"name":"\u7269\u6599\u5206\u7C7B","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1592539382001422,"name":"\u51FA\u8D27\u7EC6\u5316-\u5BA2\u6237\u7EDF\u8BA1","open":true,"icon":null,"iconSkin":null,"pId":1592539382001821},{"id":1576830009915354,"name":"\u4EF7\u683C\u7533\u8BF7","open":true,"icon":null,"iconSkin":null,"pId":1576830009617716},{"id":1576830102324678,"name":"\u53D1\u5E03\uFF0F\u7F16\u8F91\u6D88\u606F","open":true,"icon":null,"iconSkin":null,"pId":1576830101976741},{"id":1576830009617716,"name":"\u7533\u8BF7","open":true,"icon":null,"iconSkin":null,"pId":null},{"id":1576830518754859,"name":"\u6B63\u80FD\u91CF\u8D26\u53F7\u8BBE\u7F6E","open":true,"icon":null,"iconSkin":null,"pId":1576830518507293},{"id":1576830331048502,"name":"\u7269\u6599\u578B\u53F7","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1592539382041842,"name":"\u5E93\u5B58\u5206\u6790","open":true,"icon":null,"iconSkin":null,"pId":1592539382001818},{"id":1576830101976741,"name":"\u6D88\u606F\u4E2D\u5FC3","open":true,"icon":null,"iconSkin":null,"pId":null},{"id":1576830518754260,"name":"\u56FD\u5185\u4ED3\u9501\u8D27\u6743\u9650","open":true,"icon":null,"iconSkin":null,"pId":1576830518507293},{"id":1592539382001842,"name":"\u51FA\u8D27\u62A5\u8868","open":true,"icon":null,"iconSkin":null,"pId":1592539382001818},{"id":1592539382005822,"name":"\u51FA\u8D27\u7EC6\u5316-\u51FA\u8D27\u5360\u6BD4","open":true,"icon":null,"iconSkin":null,"pId":1592539382001821},{"id":1576830331258625,"name":"\u7269\u6599\u56FD\u5185\u5E93\u5B58","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1576830102433969,"name":"\u5220\u9664\u6D88\u606F","open":true,"icon":null,"iconSkin":null,"pId":1576830101976741},{"id":1576830010022199,"name":"\u5F00\u53D1\u5DE5\u5177\u7533\u8BF7","open":true,"icon":null,"iconSkin":null,"pId":1576830009617716},{"id":1576829852480747,"name":"\u5B98\u65B9\u4EA4\u671F","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576830331474734,"name":"\u672C\u5E74\u5EA6\u51FA\u8D27\u6570\u91CF","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1592539382001821,"name":"\u51FA\u8D27\u7EC6\u5316","open":true,"icon":null,"iconSkin":null,"pId":1592539382001818},{"id":1577173031332586,"name":"\u5E93\u5B58","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576830518754261,"name":"\u4F9B\u5E94\u94FE\u0026\u5E93\u5B58\u9884\u8B66\u6BD4\u4F8B","open":true,"icon":null,"iconSkin":null,"pId":1576830518507293},{"id":1592539382001818,"name":"\u62A5\u8868\u4E2D\u5FC3","open":true,"icon":null,"iconSkin":null,"pId":null},{"id":1576830160797318,"name":"\u4EF7\u683C\u8BBE\u7F6E","open":true,"icon":null,"iconSkin":null,"pId":null},{"id":1576829852861737,"name":"\u8FDB\u4EF7","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576830331363807,"name":"\u4E0A\u5E74\u5EA6\u51FA\u8D27\u6570\u91CF","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1576830334048506,"name":"\u5E02\u573A\u65B9\u5411","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1576738931829860,"name":"\u6570\u636E\u540C\u6B65","open":true,"icon":null,"iconSkin":null,"pId":null},{"id":1576830331258621,"name":"\u9999\u6E2F\u4ED3","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1576830397532753,"name":"\u6743\u9650\u7BA1\u7406","open":true,"icon":null,"iconSkin":null,"pId":null},{"id":1576830331258622,"name":"\u9999\u6E2F\u76F4\u8FD0\u4ED3","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1577157938152502,"name":"\u62A5\u4EF7","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576830331258623,"name":"\u5728\u9014\u5E93\u5B58\u6570\u91CF","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1576830518507293,"name":"\u7CFB\u7EDF\u8BBE\u7F6E","open":true,"icon":null,"iconSkin":null,"pId":null},{"id":1577173031603937,"name":"\u62A5\u4EF7\u53C2\u8003","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576830331258620,"name":"\u4F9B\u5E94\u94FE\u672A\u5B8C\u6210\u6570\u91CF","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1577157937807987,"name":"\u5F00\u53D1\u5DE5\u5177","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576830331258633,"name":"\u5BA2\u6237\u7AEF\u672A\u5B8C\u6210\u6570\u91CF","open":true,"icon":null,"iconSkin":null,"pId":1576738931829860},{"id":1576829852988791,"name":"\u5386\u53F2\u51FA\u8D27\u6570\u91CF","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576757423116672,"name":"\u7F16\u8F91\u4E3B\u63A8\u578B\u53F7","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576757423116673,"name":"\u7F16\u8F91\u89C6\u9891","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815},{"id":1576757423116675,"name":"\u6D4F\u89C8\u89C6\u9891","open":true,"icon":null,"iconSkin":null,"pId":1576757422730815}]

function parse(data) {
    let children = ret(data, null)

    function ret(_data, pid) {
        let _children = []
        let otherNode = []
        _data.forEach(node => {
            if (node.pId === pid) {
                _children.push(node)
            } else {
                otherNode.push(node)
            }
        })

        if (_children.length > 0) {
            // 排序
            _children = _children.sort(sort)

            _children.forEach(_node => {
                let node_child = ret(otherNode, _node.id)
                if (node_child.length > 0) {
                    _node.children = node_child
                }

            })   
        }
        return _children
    }

    function sort(a, b) { // 自定义排序规则
        return b.id - a.id
    }

    return children    
}

let formatData = parse(permissionList)

console.log(JSON.stringify(formatData))

converters chain

Request data
=> HttpMessageConverter
=> FormHttpMessageConverter
=> MappingJackson2HttpMessageConverter && @RequestBody
=> Yml2HttpMessageConverter(自定义)
=> Validation验证
=> 数据持久化
=> 返回json

数据流转

HttpMessageConverter

采用RequestResponseBodyMethodProcessor方法执行器,
前台提交数据,设置contentType,spring-web根据contentType,解析数据,并映射。

spring内置当HttpMessageConverter有

  • ByteArrayHttpMessageConverter – converts byte arrays
  • StringHttpMessageConverter – converts Strings
  • ResourceHttpMessageConverter – converts org.springframework.core.io.Resource for any type of octet stream
  • SourceHttpMessageConverter – converts javax.xml.transform.Source
  • FormHttpMessageConverter – converts form data to/from a MultiValueMap<String, String>.
  • Jaxb2RootElementHttpMessageConverter – converts Java objects to/from XML (added only if JAXB2 is present on the classpath)
  • MappingJackson2HttpMessageConverter – converts JSON (added only if Jackson 2 is present on the classpath)
  • MappingJacksonHttpMessageConverter – converts JSON (added only if Jackson is present on the classpath)
  • AtomFeedHttpMessageConverter – converts Atom feeds (added only if Rome is present on the classpath)
  • RssChannelHttpMessageConverter – converts RSS feeds (added only if Rome is present on the classpath)

平时我们用当最多当就是MappingJackson2HttpMessageConverterFormHttpMessageConverter

  • FormHttpMessageConverter使用的contentTypeapplication/x-www-form-urlencodedmultipart/form-data
  • MappingJackson2HttpMessageConverter使用的contentTypeapplication/json

NOTED

如果使用GET请求数据,那么使用ServletModelAttributeMethodProcessor,将不会使用转化器类。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

类型转换

  • MappingJackson2HttpMessageConverter对基本类型(包括枚举,List<> String[])支持都非常好。如果自定义类需要继承JsonDeserializer
@JsonComponent
public class PhoneJsonDeserializer extends JsonDeserializer<Phone> {
    @Override
    public Phone deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        String[] phonetArr = node.asText().split("-");
        Phone phone = new Phone();
        phone.setCode(phonetArr[0]);
        phone.setNumber(phonetArr[1]);
        return phone;
    }
}
  • GET提交和multipart/form-data实现Converter
@Component
public class PhoneConverter implements Converter<String, Phone> {

    @Override
    public Phone convert(String source) {
        if (StringUtils.isEmpty(source)) {
            return null;
        }

        String[] phonetArr = source.split("-");
        Phone phone = new Phone();
        phone.setCode(phonetArr[0]);
        phone.setNumber(phonetArr[1]);

        return phone;
    }
}

自定义转换器

定义一个可以转换application/yml的转换器
pom.xml添加

 <dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.19</version>
</dependency>
@Component
public class Yml2HttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    public Yml2HttpMessageConverter() {
        super(new MediaType[]{new MediaType("application", "yml"), new MediaType("application", "ymal")});
    }

    @Override
    protected boolean supports(Class<?> aClass) {
        return true;
    }

    @Override
    protected Object readInternal(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        Yaml yaml = new Yaml();
        return yaml.loadAs(httpInputMessage.getBody(), aClass);
    }

    @Override
    protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        Yaml yaml = new Yaml();
        OutputStream outputStream = httpOutputMessage.getBody();
        outputStream.write(yaml.dump(o).getBytes());
        outputStream.close();
    }
}

@Component注解会放在在所有converter的第一个。优先于MappingJackson2HttpMessageConverter。要改变顺序,WebMvcConfigurer中去实现方法extendMessageConverters。导致response的时候,都是yml格式,而不是json格式。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * https://www.cnblogs.com/slankka/p/11437034.html
     * 加到最后
     * @param converters
     */
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new Yml2HttpMessageConverter());
    }
}

这样response的时候,优先json格式,如果需要yml格式的返回可以手动指定

@GetMapping (produces = "application/yml")
public UserDTO getData(@RequestBody UserDTO userDTO) {
    log.info(userDTO.toString());
    return userDTO;
}

Validation

使用默认验证器

controller添加注解@Valid,model添加验证注解如:@NotBlank

    @GetMapping (produces = "application/yml")
    public UserDTO getData(@RequestBody @Valid UserDTO userDTO, BindingResult result) throws Exception {
        if (result.hasErrors()) {
            throw new Exception("验证失败");
        }
        log.info(userDTO.toString());
        return userDTO;
    }

自定义验证器

@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumberConstraint {
    String message() default "Invalid phone number";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
public class PhoneNumberValidator implements
        ConstraintValidator<PhoneNumberConstraint, Phone> {

    @Override
    public void initialize(PhoneNumberConstraint constraintAnnotation) {
    }

    /**
     * @param phone
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Phone phone, ConstraintValidatorContext context) {
        if (Objects.isNull(phone)) {
            return true;
        }

        return (phone.getCode() + "-" + phone.getNumber()).matches("\\d+[-]\\d+");
    }
}

使用注解

 @PhoneNumberConstraint
 private Phone phone;

持久化

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.9</version>
    </dependency>

application.xml

spring:
  datasource:
    name: druidDataSource
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/iis?useUnicode=true&characterEncoding=UTF-8
      username: root
      password: jkxyx205
      initialSize: 1
      minIdle: 5
      maxActive: 10
      maxWait: 60000
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true

    hibernate:
      ddl-auto: update
public class ListJsonAttributeConverter<T> implements AttributeConverter<List<T>, String> {

    @Override
    public String convertToDatabaseColumn(List<T> list) {
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }

        try {
            return JsonUtils.toJson(list);
        } catch (IOException e) {
            return null;
        }
    }

    @Override
    public List<T> convertToEntityAttribute(String json) {
        Class<?> clazz = ClassUtils.getActualTypeArgument(this.getClass())[0];

        if (StringUtils.isBlank(json)) {
            return Collections.emptyList();
        }

        try {
            return (List<T>) JsonUtils.toList(json, clazz);
        } catch (IOException e) {
            e.printStackTrace();
            return Collections.emptyList();
        }
    }
}
public class PojoJsonAttributeConverter<T> implements AttributeConverter<T, String> {

    @Override
    public String convertToDatabaseColumn(T t) {
        if (Objects.isNull(t)) {
            return null;
        }

        try {
            return JsonUtils.toJson(t);
        } catch (IOException e) {
            return null;
        }
    }

    @Override
    public T convertToEntityAttribute(String json) {
        Class<?> clazz = ClassUtils.getActualTypeArgument(this.getClass())[0];
        if (StringUtils.isBlank(json)) {
            return null;
        }

        try {
            return (T) JsonUtils.toObject(json, clazz);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

将对象以 JSON 字符串的形式持久化到数据库中,使用继承指定范型类型

public class PriceConverter extends ListJsonAttributeConverter<List<QuotationExpression>> {}
public class OrderConverter extends PojoJsonAttributeConverter<QuotationExpression> {}

指定对应的convert

@Convert(converter = PriceConverter.class)
private List<QuotationExpression> quotationExpressionList;

@Convert(converter =OrderConverter.class)
private QuotationExpression quotationExpression;

https://vladmihalcea.com/jpa-attributeconverter/

https://zenidas.wordpress.com/recipes/jpa-converter-as-json/

response JSON

public class AddressJsonSerializer extends JsonSerializer<Address> {

    @Override
    public void serialize(Address address, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        address.setAddrDetail("China "+ address.getAddrDetail());
        jsonGenerator.writeObject(address);
    }
}
    @Convert(converter = AddressConverter.class)
    @JsonSerialize(using = AddressJsonSerializer.class)
    private Address address;

Spring Security + Spring Security Oauth2

Spring Security

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--thymeleaf对security的支持-->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>${thymeleaf-extras-springsecurity5-version}</version>
</dependency>

Web端:

WebSecurityConfig

主要端配置类:WebSecurityConfig,在这个文件中可以配置以下

  • loginPage 登录页面的地址(如果没有提供,Spring Security提供了默认的登录界面)

  • handler:

    • successHandler
    • authenticationFailureUrl
    • logoutSuccessHandler
  • session处理相关配置
  • 授权管理
  • 配置新的拦截器

WebSecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled=true, prePostEnabled = true)// 控制权限注解 配合 @Secured({"ROLE_ADMIN","ROLE_USER2"})使用
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ValidateFilter validateFilter;

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().disable()
                .authorizeRequests()
                .antMatchers("/forbidden", "/kaptcha").permitAll()
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler)
                .permitAll()
                .and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll()
                .and()
                .csrf().disable()// csrf会拦截POST请求 https://www.jianshu.com/p/2c275c75c77a
                .sessionManagement().
                    invalidSessionUrl("/login")
                    .maximumSessions(1)
                    .expiredSessionStrategy(new AcExpiredSessionStrategy())
                    .sessionRegistry(sessionRegistry());


        http.addFilterBefore(validateFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/coreui/**", "/css/**", "/js/**", "/img/**", "/plugins/**", "/favicon.ico");
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
}

PasswordEncoder

密码加密处理
pom.xml

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

用户认证服务UserDetailsService

@Component
@Slf4j
public class AcUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Autowired
    private CacheManager cacheManager;

    @Autowired
    private SessionRegistry sessionRegistry;


    /**
     * 进行认证授权的工作
     *
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) {
        username = username.toLowerCase();
        Cache tryCache = cacheManager.getCache("loginMaxTry");

        Object object = tryCache.get(username);
        int loginMaxTryCount = 1;
        if (Objects.isNull(object)) {
            tryCache.put(username, loginMaxTryCount);
        } else {
            loginMaxTryCount = (Integer) object + 1;
            tryCache.put(username, loginMaxTryCount);
        }

        if (loginMaxTryCount > AuthConstants.MAX_TRY_COUNT) {
            throw new MaxTryLoginException();
        }

        UserDTO user;

        try {
            user = userService.findByUsername(username);
        } catch (DataAccessException e) {
            throw new DataAccessResourceFailureException("数据库连接出错, 请联系管理员");
        }

        if (user == null) {
            throw new UsernameNotFoundException("帐号不存在或者账号被锁定");
        }

        if (isLogin(username)) {
            log.info("LOGIN: {}已经登录,踢出登录", username );
        }

        return new AcUserDetails(user, AuthorityUtils.createAuthorityList(user.getRoles().toArray(new String[]{})));
    }

    /**
     * 用户是否处于登录状态
     * @param username
     * @return
     */
    private boolean isLogin(String username) {
        List<Object> list = sessionRegistry.getAllPrincipals();
        for (Object o : list) {
            if (StringUtils.equals(((AcUserDetails)o).getUsername(), username)) {
                return sessionRegistry.getAllSessions(o, false).size() > 0;
            }

        }

        return false;
    }
}

Spring Security OAuth2

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

在Spring Security的基础上多次两个概念
* @EnableAuthorizationServer 授权服务器
* @EnableResourceServer 资源服务器

如果授权服务器和资源服务器部署「在」同一台服务器上

  • 采用授权码模式会有问题,因为采用授权码模式需要跳转「登录」界面,需要使用WebSecurityConfigWebSecurityConfig@EnableResourceServer会有冲突。

  • 采用密码模式没有问题。不需要依赖WebSecurityConfig,因为不依赖WebSecurityConfig,需要自己构造AuthenticationManager,测试时否则会报异常。


@Bean public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { List<AuthenticationProvider> providers = new ArrayList<>(); DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(userDetailsService); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder); providers.add(daoAuthenticationProvider); return new ProviderManager(providers); }

如果授权服务器和资源服务器部署「不在」同一台服务器上

  • 授权服务器

    • WebSecurityConfig + @EnableAuthorizationServer 授权码和密码模式
    • @EnableAuthorizationServer 密码模式
  • 资源服务器
    @EnableResourceServer

授权服务器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    @Qualifier("tokenServices")
    private AuthorizationServerTokenServices tokenServices;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")                    //oauth/token_key是公开
                .checkTokenAccess("permitAll()")//oauth/check_token公开
                .allowFormAuthenticationForClients()                //密码模式:表单认证(申请令牌)
        ;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                clients.inMemory()// 使用in-memory存储
                .withClient("c1")// client_id
                .secret(passwordEncoder.encode("secret"))//客户端密钥
                .resourceIds("res1")//资源列表
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
                .scopes("all")// 允许的授权范围
                .autoApprove(false)//false跳转到授权页面
                //加上验证回调地址
                .redirectUris("http://www.baidu.com");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .userDetailsService(userDetailsService)
                .authenticationManager(authenticationManager)// 认证管理器 => 密码模式需要在认证服务器中设置 中配置AuthenticationManager
                .authorizationCodeServices(authorizationCodeServices)//授权码服务
                .tokenServices(tokenServices)// 令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }


资源服务器

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    public static final String RESOURCE_ID = "res1";

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        // 资源id
        resources.resourceId(RESOURCE_ID)
                .tokenStore(tokenStore)
                .stateless(true)
                .accessDeniedHandler((request, response, e) -> {
                    response.getWriter().write("look=>" + e);
                });
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/order/r2").hasAnyAuthority("p2")
                .antMatchers("/login*").permitAll()
                .anyRequest().authenticated()
                // 需要身份认证的时候跳转的URL(可以直接指定html)
        ;
    }
}

关于html table 固定列宽

.t3 {
            width: 100%;
            table-layout: fixed; 
        }

        .t3 td, .t3 th {
            white-space: nowrap;
            word-break: break-all;
            overflow: hidden;
            text-overflow: ellipsis;
        }
  • t1未经修饰的table;t2百分百宽度;t3固定列宽,文字过多显示省略号,如果table超过容器宽度,有滚动条。

完整代码如下。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        .container {
            width: 500px;
            background: #ccc;
            margin: 0 auto;
            overflow: auto;
        }

        table , table td, table, th{
            border: 1px solid red;
            border-collapse: collapse;
        }

        .t1 {}

        .t2 {
            width: 100%;
        }

        .t3 {
            width: 100%;
            table-layout: fixed; 
        }

        .t3 td, .t3 th {
            white-space: nowrap;
            word-break: break-all;
            overflow: hidden;
            text-overflow: ellipsis;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>t1原始table</h2>
        <table class="t1">
            <thead>
                <tr>
                    <th>hello</th>
                    <th>world</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>faff</td>
                    <td>faff</td>
                </tr>
            </tbody>
        </table>
    <br>
    <h2>t2宽度100%</h2>
        <table class="t2">
            <thead>
                <tr>
                    <th>hello</th>
                    <th>world</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>faff</td>
                    <td>faff</td>
                </tr>
            </tbody>
        </table>
<br>
<h2>t3有滚动条</h2>
        <table class="t3">
            <thead>
                <tr>
                    <th style="width: 400px;">400px</th>
                    <th style="width: 120px;">120px120px120px120px120px120px</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>400px400px400px400px400px400px400px400px400px400px400px400px400px400px400px400px400px400px400px400px400px400px</td>
                    <td>dfasfsdfasfsdfasfsdfasfsdfasfsdfasfsdfasfsdfasfsdfasfsdfasfsdfasfsdfasfs</td>
                </tr>
            </tbody>
        </table>

    </div>
</body>
</html>
  • 运行截图
    t

js动态创建css样式

function setCssText(css){ 
    if(document.all){ // document.createStyleSheet(url)
        window.style = css
        document.createStyleSheet("javascript:style")
    }else{ //document.createElement(style)
        var style = document.createElement('style')
        style.type = 'text/css'
        style.innerHTML = css
        document.getElementsByTagName('HEAD').item(0).appendChild(style)
    } 
}
setCssText('body { color: red }')