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. 使用parseJSON代替eval

    有些程序员如果没有很好的在javascript中解析json数据,往往会直接eval把json转成js对象,这时候如果json的数据中包含了被注入的恶意数据,则可能导致代码注入的问题. 正确的做法是分 ...

  2. 将Asp.Net MVC应用程序的控制器定义在单独的程序集(类库)中

    一直以来都想把控制器的代码部署到单独的程序集里.昨天研究Asp.Net MVC的源代码,偶然发现有一个奇特的类“ControllerBuilder”,MSDN上的介绍相当简略,就一句话“表示一个类,该 ...

  3. css的transition 属性

    把鼠标指针放到 div 元素上,其宽度会从 100px 逐渐变为 300px: div { width:100px; transition: width 2s; -moz-transition: wi ...

  4. div+css的前端工程师的价值体现在哪些方面?

    个人认为前端工程师正慢慢演变为产品工程师.wap app, 响应性UI等以html5技术为基础的开发将成为前端工程师的主要工作内容,解决产品跨平台跨设备的实现问题.Javascript, HTML, ...

  5. 剑指offer--面试题15--相关

    感受:清晰的思路是最重要的!!! 题目:求链表中间节点 ListNode* MidNodeInList(ListNode* pHead) { if(pHead == NULL) return NULL ...

  6. 【技术贴】解决Eclipse中SVN图标不显示

    在用Eclipse做开发的时候,用到了svn版本控制器,这天当我打开Eclipse的时候,发现项目里面的所有文件前的版本号以及状态图标都不显示了,即所有的svn图标不显示了,这是怎么回事,关掉Ecli ...

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

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

  8. HDU 1598 find the most comfortable road(枚举+并查集,类似于最小生成树)

    一开始想到用BFS,写了之后,发现有点不太行.网上查了一下别人的解法. 首先将边从小到大排序,然后从最小边开始枚举,每次取比它大的边,直到start.end属于同一个集合,即可以连通时停止.过程类似于 ...

  9. SDUT1500 Message Flood

    以前做过的用的字典树,可是貌似现在再用超内存....求解释... 问了LYN用的map函数做的,又去小小的学了map函数.... http://wenku.baidu.com/view/0b08cec ...

  10. lintcode 中等题:Min stack 最小栈

    题目 带最小值操作的栈 实现一个带有取最小值min方法的栈,min方法将返回当前栈中的最小值. 你实现的栈将支持push,pop 和 min 操作,所有操作要求都在O(1)时间内完成. 解题 可以定义 ...