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. 【BZOJ 1007】 [HNOI2008]水平可见直线

    Description 在xoy直角坐标平面上有n条直线L1,L2,...Ln,若在y值为正无穷大处往下看,能见到Li的某个子线段,则称Li为可见的,否则Li为被覆盖的.    例如,对于直线:    ...

  2. js eval()执行传参函数的写法

    .cs public class Message<T> { // 数据总数 public int? Total { get; set; } // 关键数据 public List<T ...

  3. Ado.Net实现简易(省、市、县)三级联动查询,还附加Access数据

    小弟在博客园驻园不久,初来咋到:将最近写的小程序附上,希望各位大牛们吐槽:激发对程序员围观的童鞋们,赶紧加入IT行业,如果你在上海那简称就是SHIT,哈哈题外话,以下开始切入正题: 坐公交车是旁边偶遇 ...

  4. jquery插件dataTables添加序号列

    官网方法实例: $(document).ready(function() {     var t = $('#example').DataTable({         "columnDef ...

  5. 来吧,给你的Winform列表控件画个妆

    前言 以前看别人的控件好看只有羡慕的份:以前觉得控件重绘是个很复杂的东西:以前知道MSDN很全面很专业却一直没有好好用起来: 作为初级程序猿,不能原地踏步,来吧,让我们一起把 TreeView 美化一 ...

  6. Document Set 【一】

    概括介绍: Document Set 是SharePoint2010之后出现的一个新的Feature.这个Feature的主要目的是两个: 1,是帮助 User 以一个文件的管理方式管理一个文件集合. ...

  7. sqlserver mdf ldf文件导入

    EXEC  sp_attach_db  @dbname  =  '你的数据库名', @filename1  =  'mdf文件路径(包缀名)', @filename2  =  'Ldf文件路径(包缀名 ...

  8. mysql merge

    merge 是一组 myisam 表的组合, 锁住一个 merge 表它会吧底下所有的表全给锁住. 创建只读表 )) engine = merge union (t1,t2); 创建可插入的表, (以 ...

  9. HDU 3974 Assign the task 暴力/线段树

    题目链接: 题目 Assign the task Time Limit: 15000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/O ...

  10. MacOS Cocos2d-x-3.2 创建HelloWorld项目

    开发环境: Mac OSX 10.9.3 Cocos2d-x-3.2 首先,打开终端cd到目录/cocos2d-x-3.2/tools/cocos2d-console/bin下,运行cocos.py脚 ...