spring指南学习随记(一)
一、creating aysnchronous methods(创建异步方法)
对应网址:入门指南 | 创建异步方法 --- Getting Started | Creating Asynchronous Methods
1.@JsonIgnorePropertise 该注解加在实体类上,用于配置json映射到实体类的规则(反序列化)。参数有ignoreUnknown=true,意为Jackson在反序列化过程中,如果碰到json对象结构中有的属性,实体类中没有则自动忽略。若不配置ignoreUnknown=ture,则遇到json有属性找不到对应实体类属性时,则会报错:unrecognizedpropertyException(无法识别属性错误)。
2.RestTemplate对象的用法
首先,restTemplate是为了简化restful风格的uri操作而设计的通用模版类。
自动注入和构建(通过RestTemplateBuilder):
在spring的各类组件中用该结构自动注入RestTemplate实例
private final RestTemplate restTemplate;public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {this.restTemplate = restTemplateBuilder.build();}
2.1方法举例:
User results = restTemplate.getForObject(url, User.class);
getForObject方法简化网络请求过程,url参数意为要请求的url地址,User.class意为获取的json对象要自动映射的java类。
3.@Async注解:将方法标记为异步方法,若异步方法有返回对象,就需要CompletableFuture类来作为异步操作时的对象容器。因为异步操作时,异步执行的方法不占用主线程,所以线程池中线程执行完主线程后,会通过该容器修改异步方法执行状态。
流程:当主线程执行时遇到@Async注解标注的方法之后会将内部代码移交到线程池中线程执行,而主线程则先获得CompletableFuture.completedFuture(results),当线程池执行完异步逻辑后会修改CompletableFuture对应实例状态,并加入具体实例。然后后面就可以使用.get方法获取。
注意:要使用@Async注解一定要让方法在spring管理的组件内。
3.1如何让springboot程序可以运行呢?
首先:要在程序的入口文件上加上注解@EnableAsync,这样就开启了spring的多线程能力,同时会默认使用内置的ThreadPoolTaskExecutor作为线程池,我们也可以通过在入口类中添加返回方法返回Executor对象实例来自定义线程池细节,应用启动后,spring会自动识别自定义线程池环境,为异步执行提供支持。
@Bean
public Executor taskExecutor(){ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//自定义线程池细节executor.setCorePoolSize(2);executor.setMaxPoolSize(2);executor.setQueueCapacity(500);executor.setThreadNamePrefix("GithubLookup-")executor.initialize();
return executor;
}
二、authenticating a User with LDAP(用LDAP验证用户)
对应网址:入门指南 | 使用 LDAP 验证用户 --- Getting Started | Authenticating a User with LDAP
1.引入依赖
<!--spring boot 自动配置的核心依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--LDAP的操作封装,不涉及安全方面,主要是简化crud操作和连接管理-->
<dependency><groupId>org.springframework.ldap</groupId><artifactId>spring-ldap-core</artifactId>
</dependency>
<!--上面两者的桥接组件,提供LDAP绑定认证的实现-->
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-ldap</artifactId>
</dependency>
<!--提供高性能的LDAP协议实现,支持SSL/TLS加密链接等等,纯java的LDAPv3协议栈
同时还引入了内置的LDAP服务器-->
<dependency><groupId>com.unboundid</groupId><artifactId>unboundid-ldapsdk</artifactId>
</dependency>
2.构建配置类,并填写验证过滤规则
@Configuration
class WebSecurityConfig {//自定义SpringSecurity的核心过滤链类SecurityFilterChain的验证规则@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {//调用链httpSecurity.authorizeHttpRequests(requests-> {requests.anyRequest().fullyAuthenticated();}).formLogin(Customizer.withDefaults());//用自定义器定义使用默认数据登录,获取相关资源。return httpSecurity.build();}//定义如何验证用户登录规则@Autowiredpublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth //配置使用ldapAuthentication的验证方式.ldapAuthentication()//定义在ldap服务器目录中查找的DN路径.userDnPatterns("uid={0},ou=people")//这里的ou是为了查询用户对应的组而确定应该给的权限.groupSearchBase("ou=groups").contextSource()//这里我们通过url配置访问ldap服务器,并拼接上我们的根目录路径(即DC).url("ldap://localhost:8389/dc=springframework,dc=org").and()//这里我们将用户输入的密码加密后,对比ldap中的密码进而验证密码//注意这里的lDAP服务器中的密码加密方式要和passwordEncoder一致。.passwordCompare().passwordEncoder(new BCryptPasswordEncoder()).passwordAttribute("userPassword");}
这里涉及到对LDAP结构的了解和LDIF的相关知识。
3.编写LDAP服务器的配置类(内嵌)
#LDAP相关配置#指定访问的服务器的根目录DN(类似路径)
spring.ldap.embedded.base-dn=dc=springframework,dc=org#指定初始化的数据文件
spring.ldap.embedded.ldif=classpath:test-server.ldif#指定内嵌的LDAP服务器的端口
spring.ldap.embedded.port=8389
3.1配置中指定的test-server.ldif文件内容如下(classpath:表示默认从resources目录下寻找)
dn: dc=springframework,dc=org
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: springframeworkdn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groupsdn: ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: subgroupsdn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: peopledn: ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: space cadetsdn: ou=\"quoted people\",dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: "quoted people"dn: ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: otherpeopledn: uid=ben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Ben Alex
sn: Alex
uid: ben
userPassword: $2a$10$c6bSeWPhg06xB1lvmaWNNe4NROmZiSpYhlocU/98HNr2MhIOiSt36dn: uid=bob,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword: bobspassworddn: uid=joe,ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Joe Smeth
sn: Smeth
uid: joe
userPassword: joespassworddn: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Mouse, Jerry
sn: Mouse
uid: jerry
userPassword: jerryspassworddn: cn=slash/guy,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: slash/guy
sn: Slash
uid: slashguy
userPassword: slashguyspassworddn: cn=quote\"guy,ou=\"quoted people\",dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: quote\"guy
sn: Quote
uid: quoteguy
userPassword: quoteguyspassworddn: uid=space cadet,ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Space Cadet
sn: Cadet
uid: space cadet
userPassword: spacecadetspassworddn: cn=developers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: developers
ou: developer
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: uid=bob,ou=people,dc=springframework,dc=orgdn: cn=managers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: managers
ou: manager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: cn=mouse\, jerry,ou=people,dc=springframework,dc=orgdn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: submanagers
ou: submanager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
4.最后设置Controller页面(就是正常业务逻辑的controller)
@RestController
class HomeController {@GetMapping("/")public String index(){return "欢迎来到我的应用";}
}
5.验证,启动服务并访问应用服务后,会自动跳转到LDAP服务验证界面(我们之前配置过使用的是默认的界面),输入用户:ben 密码:benspassword,即可通过授权。
6.bug出现与解读:
6.1因为在自动配置SecurityFilterChain过程中我注入bean的方法名好巧不巧使用的是springSecurityFilterChain,这就导致报错:说找不到我方法中自动注入的HttpSecurity.
6.2原因分析:因为我使用的是SpringBoot3.5.5对应的SpringSecurity的版本是6.x,6版本以后虽然强制用户要自定义过滤链,但是在旧版本中若用户没有定义SecurityFilterChain则会自动注入默认的过滤链,好巧不巧,我自定义的方法名又是springSecurityFilterChain,并且我还没有在@Bean中用name属性自定义bean名称,spring自动使用方法名作为bean名。另外SpringSecurity是兼容旧版本的,所以当检测到有名称为springSecurityFilterChain时,会默认其具有最高优先级,进行提前注入,而恰恰因为此,HttpSecurity还没有被注册,就没有bean实例,故而报错。
6.3修改了方法名为securityFilterChain,则成功!
7.成果
访问localhost:1314/(我的服务器地址)会自动跳转到localhost:1314/login进行登录,从而调用验证逻辑,
此时
若输入正确的账户和密码:ben benspassword则跳转到正确映射
若密码或用户名输入错误,则提示: