Drill的Java后端
Table of Contents
Java后端负责业务逻辑,数据持久化(数据库),安全等等.目前接触到的内容有
账号注册登录管理,本地化设置等等.将整个后端当做一个应用,那么在主目录
下有一个 Application.java
类作为整个应用的入口.这个类最基本的应用需要
@ComponentScan
和 @EnableAutoConfiguration
两个标签来声明自动配
置和扫描组件;同时在 main
函数里实例化一个 SpringApplication
对象
并调用对象函数 run()
就行了. 目前的程序里还用
app.setAdditionalProfiles
在没有命令行参数的时候默认加载dev配置.
1 环境与配置加载
1.1 配置文件
SpringApplication
会自动搜索以下路径来寻找
application.properties
作为配置文件.
- 当前路径下地
/config
子文件夹 - 当前路径
classpath:/config
classpath
同时,SpringApplication还支持 YAML 文件,即 applicaiton.yml.
函数 SpringApplication.setAdditionalProfiles("dev")
则会额外加载
application-dev.yml (或者 application-dev.properties), 而不是
dev.yml.
1.2 获取配置内容
当我们有类继承了EnvironmentAware这个接口的时候,框架会自动调用这个类
的 setEnvironment
方法,将一个 Environment
对象交给这个类的对象
从而使这个类获得配置文件中提供的参数. LocaleConfiguration
和
MailConfiguration
两个类展示了相关的编程方法.
public class MailConfiguration implements EnvironmentAware { public void setEnvironment(Environment environment) { this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_SPRING_MAIL); } }
另一种方法是直接 @Inject
一个 Environment
的对象.
public class MailService { @Inject private Environment env; }
<–这种方法跟之前所介绍的有什么区别,暂时不知道.–>
1.3 根据配置进行渲染
LocalResolver
和 Interceptor
. 在接受请求的时候, Interceptor
拦截请求中特定的参数值,并且调用 LocalResolver
的函数来进行相关渲
染处理.在 LocaleConfiguration
中,龙伟权通过定义Bean来指定特定的
LocalResolver
, 即 AngularCookieLocaleResolver
.
问题:
LocaleConfiguration
和ThymeleafConfiguration
都有方法返回MessageSource
,MailService
里面貌似引用的是LocaleConfiguration
返回的对象,为什么?
2 持久化
对象的关联问题.Mongodb本身作为NoSql的典范,内部以文件和类似json的数
据形式来储存数据.Mongodb中一个Document可以看做关系数据库中的一行数
据,一个Repository可以看做关系数据库中的一个表(table).目前我们有三个
标记为 @Document
的类 PersistentToken,Task,User
和对应的
Repository
类.
3 业务层
3.1 Controller
控制器类都在org.team.drill.web下面,统一以 XXXResource 命名.
3.1.1 AccountResource
- registerAccount 注册账户,接受新账户信息,验证账户,创建账户,发送激活邮件.
- activateAccount 激活账户,通过激活key来确定当前User.
- isAuthenticate
这个什么时候会用到?
- HttpServletRequest request.getRemoteUser()
- getAccount 这个貌似是浏览账户信息的时候用, 返回 UserDTO
- saveAccount 这个是修改用户信息用, email 地址必须唯一
- changePassword (newPassword) 这个是修改用户密码用
- getCurrentSession 貌似是获得所有当前用户的登录记录(多地自动登录产生多个记录)
- invalideSession 取消某个登录记录(已登录)
3.1.2 UserResource
- getUser 通过String login获取user, 直接返回 User
登录功能是在 SecurityConfig 中间接实现的.
3.2 Service
Service 里面通过 SecurityContextHolder 来获取当前用户.
3.3 DTO
位于 web.dto 包下面的 UserDTO
类型,似乎是用来作为JS交互的数据
对象?但是内容上跟 domain 里的 User
差不多,用来保证安全.在更复
杂的应用环境中有用.
4 Security
不论是Service还是Resource,都是靠 SecurityUtils.getCurrentLogin()
来获取当前用户的.那么问题来了,SecurityUtils如何知道当前用户是谁?
public final class SecurityUtils { private SecurityUtils() { } /** * Get the login of the current user. */ public static String getCurrentLogin() { SecurityContext securityContext = SecurityContextHolder.getContext(); Authentication authentication = securityContext.getAuthentication(); UserDetails springSecurityUser = null; String userName = null; if(authentication != null) { if (authentication.getPrincipal() instanceof UserDetails) { springSecurityUser = (UserDetails) authentication.getPrincipal(); userName = springSecurityUser.getUsername(); } else if (authentication.getPrincipal() instanceof String) { userName = (String) authentication.getPrincipal(); } } return userName; } }
4.1 SecurityContextHolder
包含安全上下文信息,并且默认为/ThreadLocal/,即对于一个线程采用一
个统一的安全上下文信息.同时,=SecurityContextHolder= 中还储存了当前
与App进行交互的主体(principal)的详情,用 Authentication
对象来表
示.上面的代码示例中体现的是获取当前用户信息的标准用法.
<–spring应用内部线程到底是如何管理 的?–>
4.2 UserDetails
在以上代码片段中我们可以看到一个特殊的类 UserDetails
,在获取当前主
体的时候,返回的可能是一个 String 或者一个 UserDetails 的对象.那
么这个/UserDetails/到底是什么东西,为什么它的方法 getUsername()
刚
好能包含我们的 User
类所需的 login?
UserDetails 是一个接口,包含了 getPassword()
, getUsername()
等
一系列方法. 在 UserDetails 中加入我们定义的 login,这是靠实现
UserDetailsService
接口来做到的. 这个接口只包含一个方法,这个方法
接受一个 String 参数,并返回一个实现了 UserDetails
接口的对象.目
前在org.team.drill.security包中的 UserDetailsService
类里面,龙伟
权实现了这个方法,并且将 org.team.drill.domain.User
的 login 包
装到实现了 UserDetails 接口的
org.springframework.security.core.userdetails.User
类中.
到这里,我试着把 SecurityContextHolder 到 UserDetails 串起来. 个
人认为,在通过
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
来获取 UserDetails 的时候,spring通过调用实现了
org.springframework.security.core.userdetails.UserDetailsService
接口的对象的方法来尝试获取 UserDetails, 这个方法本身接受一个
String参数. 又绕回来了,这个String参数是
从哪来的?依靠http协议?还是session?
因为某种原因,
getPrincipal 方法返回的是一个 Object,那么实际上给出了一种可能性,就
是通过某个实现了 UserDetails 的类使用额外的业务逻辑.我们可以创建
一个实现了 UserDetals 的类 A,在 UserDetailsService 中返回 A
的对象,然后将 getPrincipal() 返回的对象强制转换为 A 并调用相关
方法.当然,强制转换类型总是有风险的.
4.3 GrantedAuthority
Authentication
提供两个重要的方法,第一个是上面说过的
getPrincipal,另一个则是 getAuthorities .它返回一个
GrantedAuthoriy 对象数组.顾名思义,这是用来管理用户权限的.在
UserDetailsService 里面进行加载.目前drill中加载的是
SimpleGrantedAuthority
数组,每个 SimpleGrantedAuthority 里面包
含一个字符串,代表某种权限(自定义于
org.team.drill.security.Authority
中).
4.4 其它
用 random.nextBytes
来产生 PersistentToken 的序列号,不怕重复?
虽然16位Byte确实已经很大(10亿的4次方).