Java后端负责业务逻辑,数据持久化(数据库),安全等等.目前接触到的内容有 账号注册登录管理,本地化设置等等.将整个后端当做一个应用,那么在主目录 下有一个 Application.java 类作为整个应用的入口.这个类最基本的应用需要 @ComponentScan@EnableAutoConfiguration 两个标签来声明自动配 置和扫描组件;同时在 main 函数里实例化一个 SpringApplication 对象 并调用对象函数 run() 就行了. 目前的程序里还用 app.setAdditionalProfiles 在没有命令行参数的时候默认加载dev配置.

1 环境与配置加载

1.1 配置文件

SpringApplication 会自动搜索以下路径来寻找 application.properties 作为配置文件.

  1. 当前路径下地 /config 子文件夹
  2. 当前路径
  3. classpath:/config
  4. classpath

同时,SpringApplication还支持 YAML 文件,即 applicaiton.yml. 函数 SpringApplication.setAdditionalProfiles("dev") 则会额外加载 application-dev.yml (或者 application-dev.properties), 而不是 dev.yml.

1.2 获取配置内容

当我们有类继承了EnvironmentAware这个接口的时候,框架会自动调用这个类 的 setEnvironment 方法,将一个 Environment 对象交给这个类的对象 从而使这个类获得配置文件中提供的参数. LocaleConfigurationMailConfiguration 两个类展示了相关的编程方法.

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 根据配置进行渲染

LocalResolverInterceptor. 在接受请求的时候, Interceptor 拦截请求中特定的参数值,并且调用 LocalResolver 的函数来进行相关渲 染处理.在 LocaleConfiguration 中,龙伟权通过定义Bean来指定特定的 LocalResolver, 即 AngularCookieLocaleResolver.

问题:

  1. LocaleConfigurationThymeleafConfiguration 都有方法返回 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.Userlogin 包 装到实现了 UserDetails 接口的 org.springframework.security.core.userdetails.User 类中.

到这里,我试着把 SecurityContextHolderUserDetails 串起来. 个 人认为,在通过 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次方).