REST API对于前后端或后端与后端之间通讯是一个好的接口,而单页应用Single Page Applications (SPA)非常流行. 我们依然以trackr为案例,这是一个跟踪工作时间 请假 差旅花费 发票等管理系统。前端使用AngularJS,后端是基于Java 8 与Spring 4,API是通过OAuth2加密.

该项目已开源,地址戳这里,后端代码下载:here (backend) ,前端下载: here (frontend).

1. Gradle和Spring Boot

基于Spring Boot的基本Gradle配置如下:

apply plugin: 'java'
apply plugin: 'spring-boot'
jar {
baseName = 'jaxenter-example'
version = '1.0'
}
dependencies {
compile("org.springframework.boot:spring-boot-starter")
compile("org.springframework.boot:spring-boot-starter-logging")
}

下面是Spring
Boot的基本代码,主要魔力是 @EnableAutoConfiguration

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application implements
CommandLineRunner {
private Logger logger =
LoggerFactory.getLogger(Application.class);
@Autowired
private SomeService someService;
@Override
public void run(String... args) throws
Exception {
String foo = someService.foo();
logger.info("SomeService returned
{}", foo);
}
public static void main(String[] args) {
SpringApplication.run(Application.class,
args);
}
}

Spring
服务的基本代码如下:

@Service
public class SomeService {
private Logger logger =
LoggerFactory.getLogger(SomeService.class);
public String foo() {
logger.debug("Foo has been called");
return "bar";
}
}

如果我们增加@EnableScheduling,那么以@Scheduled的方法将定期自动执行。

好了,我们通过Gradle可以打包得到一个Jar包,将其部署到Docker等容器中作为微服务。

2.增加持久层和REST服务

我们如果将HSQL的驱动包加入系统Classpath,Spring Boot会自动发现它加载,同时我们需要使用Spring Data,在Build.gradle中加入:

compile("org.springframework.boot:spring-boot-starter-data-jpa")
runtime("org.hsqldb:hsqldb")
compile("org.projectlombok:lombok:1.14.8")

编写下面仓储类使用Spring Data:

@Configuration
@EnableJpaRepositories
public class PersistenceConfiguration extends
JpaRepositoryConfigExtension {
// I added some code to put two persons into
the database here.
}

因为我们之前已经激活Spring进行组件自动扫描,因此这个类将会被Spring自动发现加载,下面我们编写实体类:

@Entity
@Data
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
}
public interface PersonRepository extends
JpaRepository<Person, Long> {
List<Person> findByFirstNameLike(String
firstName);
}

现在我们需要访问数据表persons,能够根据第一个名称查询,其他基本方法Spring Data JPA 都会提供.

现在需要加入一些依赖,改变仓储一行代码以便实现:

1.
通过HTTP实现person的增删改
2. 分页查询persons
3. 用户查找

gradle一行加入如下:

compile("org.springframework.boot:spring-boot-starter-data-rest")

PersonRepository
需要一个新的注解:

List<Person> findByFirstNameLike(@Param("firstName")
String firstName);

如果启动我们的应用,下面通过curl访问API应该可以工作:

curl localhost:8080
curl localhost:8080/persons
curl -X POST -H "Content-Type:
application/json" -d "{\"firstName\":
\"John\"}"
localhost:8080/persons
curl localhost:8080/persons/search/findByFirstNameLike\?firstName=J%25
curl -X PUT localhost:8080/persons/1 -d
"{\"firstName\": \"Jane\"}" -H
"Content-Type:
application/json"
curl -X DELETE localhost:8080/persons/1

现在REST是公开的任何人可以访问。下面加入安全。

3.用Spring scecurity加密REST

现在为了支持Spring security,在gradle配置加入:

compile("org.springframework.boot:spring-boot-starter-security")

启动应用后,在日志中看到:

Using default security password:
ed727172-deff-4789-8f79-e743e5342356

此时用户名是user,上面是密码,那么我们可以使用这对用户名密码访问REST:

curl user:ed727172-deff-4789-8f79-e743e5342356@localhost:8080/persons

当然在真实项目中,我们需要多用户和多角色。

比如我们加入admin角色,只有admin才能查询所有人和查找他们。首先我们要加入自己的安全配置:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled =
true)
@EnableWebSecurity
public class SecurityConfiguration extends
WebSecurityConfigurerAdapter {
@Autowired
private FakeUserDetailsService
userDetailsService;
@Override
protected void
configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http)
throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated();
http.httpBasic();
http.csrf().disable();
}
}

下面的服务将用户名映射到我们自己数据表的人名:

@Service
public class FakeUserDetailsService implements
UserDetailsService {
@Autowired
private PersonRepository personRepository;
@Override
public UserDetails loadUserByUsername(String
username) throws
UsernameNotFoundException {
Person person =
personRepository.findByFirstNameEquals(username);
if (person == null) {
throw new UsernameNotFoundException("Username
" + username + " not
found");
}
return new User(username,
"password", getGrantedAuthorities(username));
}
private Collection<? extends
GrantedAuthority> getGrantedAuthorities(String
username) {
Collection<? extends GrantedAuthority>
authorities;
if (username.equals("John")) {
authorities = asList(() ->
"ROLE_ADMIN", () -> "ROLE_BASIC");
} else {
authorities = asList(() ->
"ROLE_BASIC");
}
return authorities;
}
}

这里你会看到Java 8的lambda的使用。

最后我们改变Spring Data使用我们自己的安全定义:

@Override
@PreAuthorize("hasRole('ROLE_ADMIN')")
Page<Person> findAll(Pageable pageable);
@Override
@PostAuthorize("returnObject.firstName ==
principal.username or
hasRole('ROLE_ADMIN')")
Person findOne(Long aLong);
@PreAuthorize("hasRole('ROLE_ADMIN')")
List<Person> findByFirstNameLike(@Param("firstName")
String firstName);

这里定义了admin可以查询所有人和查找某个人。

我们重启动该应用后,可以测试一下:

% curl Mary:password@localhost:8080/persons/1
{"timestamp":1414951322459,"status":403,"error":"Forbidden","exception":"org.springfra
mework.security.access.AccessDeniedException","message":"Access
is
denied","path":"/persons/1"}

如果我们使用John访问marry的账户,会得到403错误。

你会注意到缺省安全码还是存在,但是已经失效。

以上只有查询GET,如果需要PUT POST,我们也需要增加安全检查:

@Component
@RepositoryEventHandler(Person.class)
public class PersonEventHandler {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@HandleBeforeSave
public void checkPUTAuthority(Person person) {
// only security check
}
}

现在创建和删除都有安全检查了。

4.增加OAuth

我们需要使用Spring Security
OAuth.实现OAuth2。下面我们首先编制一个OAuth客户端:

@Configuration
@EnableAuthorizationServer
public class OAuthConfiguration extends
AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Override
public void
configure(AuthorizationServerEndpointsConfigurer endpoints) throws
Exception {
endpoints.tokenStore(tokenStore());
}
@Override
public void
configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("curl")
.authorities("ROLE_ADMIN")
.resourceIds("jaxenter")
.scopes("read", "write")
.authorizedGrantTypes("client_credentials")
.secret("password")
.and()
.withClient("web")
.redirectUris("http://github.com/techdev-solutions/")
.resourceIds("jaxenter")
.scopes("read")
.authorizedGrantTypes("implicit");
}
}

这是从 /oauth/token获得token,这个客户端将用户发往/oauth/authorize进行授权,授权用户可以访问服务器的资源,这些端点和Web页面都包含在Spring Security OAuth中。

在我们的person能够登录之前加入如下配置:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends
WebSecurityConfigurerAdapter {
@Override
protected void
configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("John").roles("ADMIN").password("password")
.and()
.withUser("Mary").roles("BASIC").password("password");
}
@Override
protected void configure(HttpSecurity http)
throws Exception {
http.authorizeRequests().antMatchers("/**").authenticated()
.and().httpBasic().realmName("OAuth
Server");
}
}

现在授权服务器已经完成,下面现在让我们的REST API知道它已经是一个资源服务器,使用同样的将数据库token作为授权服务器。

@Configuration
@EnableResourceServer
public class OAuthConfiguration extends
ResourceServerConfigurerAdapter {
@Value("${oauth_db}")
private String oauthDbJdbc;
@Bean
public TokenStore tokenStore() {
DataSource tokenDataSource =
DataSourceBuilder.create().driverClassName("org.sqlite.JDBC").url(oauthDbJdbc).build()
;
return new JdbcTokenStore(tokenDataSource);
}
@Override
public void
configure(ResourceServerSecurityConfigurer resources) throws Exception
{
resources.resourceId("jaxenter")
.tokenStore(tokenStore());

这个配置将替代老的HttpSecurity,老的HttpSecurity失效。

现在应用必须重新启动,我们配置授权服务器运行在8081端口,如果有必要初始化token数据库,当授权服务器已经开始运行,我们能使用下面基本授权方式请求一个token:

curl
curl:password@localhost:8081/oauth/token\?grant_type=client_credentials

作为响应,我们得到一个token,如下面方式使用:

curl
-H "Authorization: Bearer $token" localhost:8080

我们给定cURL客户端以admin角色和读写范围,这样一切就OK了。

下一步,在web客户端浏览器中,我们访问URL
http://localhost:8081/oauth/authorize?client_id=web&response_type=token

作为John登入,得到一个授权页面,如果我们有一个实际已经配置的web客户端,那么就会返回URL。

trackr: An AngularJS app with a Java 8 backend – Part IV 实践篇的更多相关文章

  1. trackr: An AngularJS app with a Java 8 backend – Part II

    该系列文章来自techdev The Frontend 在本系列的第一部分我们已经描述RESTful端建立在Java 8和Spring.这一部分将介绍我们的第一个用 AngularJS建造的客户端应用 ...

  2. trackr: An AngularJS app with a Java 8 backend – Part I

    该系列文章来自techdev 我想分享在techdev公司开发的项目-trackr-的一些最新的见解.trackr是一个用来跟踪我们的工作时间,创建报告和管理请假的web应用程序.做这个程序的目的有两 ...

  3. trackr: An AngularJS app with a Java 8 backend – Part III

    这是最后我们对trackr系列的一部分.在过去的两的博文中,我们已经向您展示我们使用的工具和框架构建后端和前端.如果你错过了前面的帖子现在你可能会想读他们赶上来. Part I – The Backe ...

  4. 2.1:你的第一个AngularJS App

    本章,带你体验一个简单的开发流程,将一个静态的使用模拟数据的应用,变成具有AngularJS特性的动态web应用.在6-8章,作者将展示如何创建一个更复杂,更真实的AngularJS应用. 1.准备项 ...

  5. eclipse java项目中明明引入了jar包 为什么项目启动的时候不能找到jar包 项目中已经 引入了 com.branchitech.app 包 ,但时tomcat启动的时候还是报错? java.lang.ClassNotFoundException: com.branchitech.app.startup.AppStartupContextListener java.lang.ClassN

    eclipse java项目中明明引入了jar包 为什么项目启动的时候不能找到jar包 项目中已经 引入了 com.branchitech.app 包 ,但时tomcat启动的时候还是报错?java. ...

  6. ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app

    转载:http://bitoftech.net/2014/08/11/asp-net-web-api-2-external-logins-social-logins-facebook-google-a ...

  7. IOS IAP APP内支付 Java服务端代码

    IOS IAP APP内支付 Java服务端代码   场景:作为后台需要为app提供服务,在ios中,app内进行支付购买时需要进行二次验证. 基础:可以参考上一篇转载的博文In-App Purcha ...

  8. APP接口自动化测试JAVA+TestNG(一)之框架环境搭建

    前言 好久不曾写点啥,去年换到新公司组测试团队与培养建设花费大量时间与精力,终于架构成型与稳定有时间可以打打酱油了.很久没有总结点啥,提笔想写的内容太多,先放APP接口自动化的内容吧,这个估计大家比较 ...

  9. 微信APP支付(Java后台生成签名具体步骤)

    public class PayCommonUtil { //定义签名,微信根据参数字段的ASCII码值进行排序 加密签名,故使用SortMap进行参数排序 public static String ...

随机推荐

  1. SNV ConnerStore使用说明

    1.  上传自己新建的文件新建的类文件 后面的 会有A标示要先 Add To Working copy 再点击提交 2. 上传第三方库时 默认SVN是忽略.a文件的要找到.a文件把他设置成不是忽略的通 ...

  2. 《Power》读书笔记

    原创作品 版权所有 转载请注明出处! 序言 权力是“争”来的,不是“等”来的. 会计.工商管理.营销和销售部门.财务人员(背景).企业咨询小组 在位晋升而竞争的时候,对于公平竞争原则,有些人会采取变通 ...

  3. HTML5 本地裁剪图片

    下面奉上我自己写的一个demo,代码写得比较少,很多细节不会处理.如果有不得当的地方恳请指教,谢谢啦 ^_^ ^_^   功能实现步奏:   一:获取文件,读取文件并生成url   二:根据容器的大小 ...

  4. 【贪心】Bzoj 2457:[BeiJing2011]双端队列

    2457: [BeiJing2011]双端队列 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 209  Solved: 95[Submit][Stat ...

  5. [原]android不支持命名的semaphore

    之前sem_open在iOS上, 创建命名的semaphore没有问题 (iOS不支持匿名的semaphore), 但是现在Android平台的sem_open时候报错,返回ENOSYS. 命名的se ...

  6. [nowCoder] 完全二叉树结点数

    给定一棵完全二叉树的头节点head,返回这棵树的节点个数.如果完全二叉树的节点数为N,请实现时间复杂度低于O(N)的解法. 分析:遍历的话不管是前序.中序.后序还是层次都是O(N),低于O(N)只能是 ...

  7. RedHat Linux下注册Apache为系统服务并设为开机启动

    1.系统环境: 操作系统:Red Hat Enterprise Linux Server release 5.4 Apache版本:httpd-2.2.19 2.注册服务 #将apachectl复制到 ...

  8. unit3d 4.6 document open solution

    发现4.6 的 本地 文档字体解析采用 fonts.googleapis.com ,可是google 自古就与天朝不在一个鼻孔,所以你打开往往需要半天. 网上查了一下,把所有本地的*.html 文档  ...

  9. 轻轻修改配置文件完成 OpenStack 监控

    当我们使用虚拟化云平台 OpenStack 时,必然要时时监控其虚拟机性能,随着近年企业级数据中心的不断发展,像混合虚拟化环境的业务需求也在持续增长中,因而也随之带来的监控需求更显重要,所以小编带来一 ...

  10. CentOS下使用cmake编译安装mysql

    一.下载安装所必需的依赖包 1.因为高版本mysql都用cmake安装,所以下载cmake wget http://www.cmake.org/files/v3.0/cmake-3.0.1.tar.g ...