`
wbj0110
  • 浏览: 1610312 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

HOW-TO: Get started quickly with Spring 4.0 to build a simple REST-Like API (wal

阅读更多

HOW-TO: Get started quickly with Spring 4.0 to build a simple REST-Like API (walkthrough)

Yet another tutorial about creating Web API with Spring MVC. Not really sophisticated. Just a walkthrough. The resulting app will serve simple API, will use Mongo as its persistence and it will be secured with Spring Security.

Getting started – POM

Of course, I am still a huge fan of Maven so the project is Maven based. Since there is Spring 4.0 RC2 available, I decided to utilize its new dependency managament which results in the following pom.xml: It is quite simple as it goes to Spring MVC application. The new thing is thedependencyManagement element. More explanation on that can be found here: http://spring.io/blog/2013/12/03/spring-framework-4-0-rc2-available

Configuration

The application is configured using JavaConfig. I divided it into several parts:

ServicesConfig

01 @Configuration
02 public class ServicesConfig {
03  
04     @Autowired
05     private AccountRepository accountRepository;
06  
07     @Bean
08     public UserService userService() {
09         return new UserService(accountRepository);
10     }
11  
12     @Bean
13     public PasswordEncoder passwordEncoder() {
14         return NoOpPasswordEncoder.getInstance();
15     }
16 }

No component scan. Really simple.

PersistenceConfig

A MongoDB configuration with all available repositories. In this simple application we have only one repository, so the configuration is really simple.

01 @Configuration
02 class PersistenceConfig {
03  
04     @Bean
05     public AccountRepository accountRepository() throws UnknownHostException {
06         return new MongoAccountRepository(mongoTemplate());
07     }
08  
09     @Bean
10     public MongoDbFactory mongoDbFactory() throws UnknownHostException {
11         return new SimpleMongoDbFactory(new Mongo(), "r");
12     }
13  
14     @Bean
15     public MongoTemplate mongoTemplate() throws UnknownHostException {
16         MongoTemplate template = new MongoTemplate(mongoDbFactory(), mongoConverter());
17         return template;
18     }
19  
20     @Bean
21     public MongoTypeMapper mongoTypeMapper() {
22         return new DefaultMongoTypeMapper(null);
23     }
24  
25     @Bean
26     public MongoMappingContext mongoMappingContext() {
27         return new MongoMappingContext();
28     }
29  
30     @Bean
31     public MappingMongoConverter mongoConverter() throws UnknownHostException {
32         MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory(), mongoMappingContext());
33         converter.setTypeMapper(mongoTypeMapper());
34         return converter;
35     }
36 }

SecurityConfig

In theory, Spring Security 3.2 can be fully configured with JavaConfig. For me it is still a theory, so I use XML here:

1 @Configuration
2 @ImportResource("classpath:spring-security-context.xml")
3 public class SecurityConfig {}

And the XML: As you can see basic authentication will be used for the API.

WebAppInitializer

We don’t want the web.xml so we use the following code to configure the web application:

01 @Order(2)
02 public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
03  
04     @Override
05     protected String[] getServletMappings() {
06         return new String[]{"/"};
07     }
08  
09     @Override
10     protected Class[] getRootConfigClasses() {
11         return new Class[] {ServicesConfig.class, PersistenceConfig.class, SecurityConfig.class};
12     }
13  
14     @Override
15     protected Class[] getServletConfigClasses() {
16         return new Class[] {WebMvcConfig.class};
17     }
18  
19     @Override
20     protected Filter[] getServletFilters() {
21         CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
22         characterEncodingFilter.setEncoding("UTF-8");
23         characterEncodingFilter.setForceEncoding(true);
24         return new Filter[] {characterEncodingFilter};
25     }
26  
27     @Override
28     protected void customizeRegistration(ServletRegistration.Dynamic registration) {       
29         registration.setInitParameter("spring.profiles.active""default");
30     }
31 }

WebAppSecurityInitializer

New to Spring Security 3.

1 @Order(1)
2 public class WebAppSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
3  
4 }

WebMvcConfig

The dispatcher servlet configuration. Really basic. Only crucial components to build a simple API.

01 @Configuration
02 @ComponentScan(basePackages = { "pl.codeleak.r" }, includeFilters = {@Filter(value = Controller.class)})
03 public class WebMvcConfig extends WebMvcConfigurationSupport {
04  
05     private static final String MESSAGE_SOURCE = "/WEB-INF/i18n/messages";
06  
07     @Override
08     public RequestMappingHandlerMapping requestMappingHandlerMapping() {
09         RequestMappingHandlerMapping requestMappingHandlerMapping =super.requestMappingHandlerMapping();
10         requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
11         requestMappingHandlerMapping.setUseTrailingSlashMatch(false);
12         return requestMappingHandlerMapping;
13     }
14  
15     @Bean(name = "messageSource")
16     public MessageSource messageSource() {
17         ReloadableResourceBundleMessageSource messageSource = newReloadableResourceBundleMessageSource();
18         messageSource.setBasename(MESSAGE_SOURCE);
19         messageSource.setCacheSeconds(5);
20         return messageSource;
21     }
22  
23     @Override
24     public Validator getValidator() {
25         LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
26         validator.setValidationMessageSource(messageSource());
27         return validator;
28     }
29  
30     @Override
31     public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
32         configurer.enable();
33     }
34 }

And that’s the config. Simple.

IndexController

To verify the config is fine, I created an IndexController, that serves simple “Hello, World” like text:

01 @Controller
02 @RequestMapping("/")
03 public class IndexController {
04  
05     @RequestMapping
06     @ResponseBody
07     public String index() {
08         return "This is an API endpoint.";
09     }
10 }

As you run the application, you should see this text in the browser.

Building the API

UserService

To finish up with the Spring Security configuration, one part is actually still needed: UserService which instance was created earlier:

01 public class UserService implements UserDetailsService {
02  
03     private AccountRepository accountRepository;
04  
05     public UserService(AccountRepository accountRepository) {
06         this.accountRepository = accountRepository;
07     }
08  
09  @Override
10  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
11   Account account = accountRepository.findByEmail(username);
12   if(account == null) {
13    throw new UsernameNotFoundException("user not found");
14   }
15   return createUser(account);
16  }
17  
18  public void signin(Account account) {
19   SecurityContextHolder.getContext().setAuthentication(authenticate(account));
20  }
21  
22  private Authentication authenticate(Account account) {
23   return new UsernamePasswordAuthenticationToken(createUser(account), null, Collections.singleton(createAuthority(account))); 
24  }
25  
26  private User createUser(Account account) {
27   return new User(account.getEmail(), account.getPassword(), Collections.singleton(createAuthority(account)));
28  }
29  
30  private GrantedAuthority createAuthority(Account account) {
31   return new SimpleGrantedAuthority(account.getRole());
32  }
33  
34 }

The requirement was to build an API endpoint that handles 3 methods: gets currently logged in user, gets all users (not really safe), creates a new account. So let’s do it.

Account

Account will be our first Mongo document. It is really easy one:

01 @SuppressWarnings("serial")
02 @Document
03 public class Account implements java.io.Serializable {
04  
05     @Id
06     private String objectId;
07  
08     @Email
09     @Indexed(unique = true)
10     private String email;
11  
12     @JsonIgnore
13     @NotBlank
14     private String password;
15  
16     private String role = "ROLE_USER";
17  
18     private Account() {
19  
20     }
21  
22     public Account(String email, String password, String role) {
23         this.email = email;
24         this.password = password;
25         this.role = role;
26     }
27  
28    // getters and setters
29 }

Repository

I started with the interface:

1 public interface AccountRepository {
2  
3     Account save(Account account);
4  
5     List findAll();
6  
7     Account findByEmail(String email);
8 }

And later with its Mongo implementation:

01 public class MongoAccountRepository implements AccountRepository {
02  
03     private MongoTemplate mongoTemplate;
04  
05     public MongoAccountRepository(MongoTemplate mongoTemplate) {
06         this.mongoTemplate = mongoTemplate;
07     }
08  
09     @Override
10     public Account save(Account account) {
11         mongoTemplate.save(account);
12         return account;
13     }
14  
15     @Override
16     public List findAll() {
17         return mongoTemplate.findAll(Account.class);
18     }
19  
20     @Override
21     public Account findByEmail(String email) {
22         return mongoTemplate.findOne(Query.query(Criteria.where("email").is(email)), Account.class);
23     }
24 }

API Controller

So we are almost there. We need to serve the content to the user. So let’s create our endpoint:

01 @Controller
02 @RequestMapping("api/account")
03 class AccountController {
04  
05     private AccountRepository accountRepository;
06  
07     @Autowired
08     public AccountController(AccountRepository accountRepository) {
09         this.accountRepository = accountRepository;
10     }
11  
12     @RequestMapping(value = "current", method = RequestMethod.GET)
13     @ResponseStatus(value = HttpStatus.OK)
14     @ResponseBody
15     @PreAuthorize(value = "isAuthenticated()")
16     public Account current(Principal principal) {
17         Assert.notNull(principal);
18         return accountRepository.findByEmail(principal.getName());
19     }
20  
21     @RequestMapping(method = RequestMethod.GET)
22     @ResponseStatus(value = HttpStatus.OK)
23     @ResponseBody
24     @PreAuthorize(value = "isAuthenticated()")
25     public Accounts list() {
26         List accounts = accountRepository.findAll();
27         return new Accounts(accounts);
28     }
29  
30     @RequestMapping(method = RequestMethod.POST)
31     @ResponseStatus(value = HttpStatus.CREATED)
32     @ResponseBody
33     @PreAuthorize(value = "permitAll()")
34     public Account create(@Valid Account account) {
35         accountRepository.save(account);
36         return account;
37     }
38  
39     private class Accounts extends ArrayList {
40         public Accounts(List accounts) {
41             super(accounts);
42         }
43     }
44 }

I hope you noticed that we talk directly to the repository, so the passwords will not be encoded. Small detail to be fixed later on, if you wish. For now it is OK.

Finishing up

The last think I needed was some error handler so the consumer can see error messages in JSON instead of HTML. This is simple with Spring MVC and @Controller advice.

01 @ControllerAdvice
02 public class ErrorHandler {
03  
04     @ExceptionHandler(value = Exception.class)
05     @ResponseStatus(HttpStatus.BAD_REQUEST)
06     @ResponseBody
07     public ErrorResponse errorResponse(Exception exception) {
08         return new ErrorResponse(exception.getMessage());
09     }
10  
11 }
12  
13 public class ErrorResponse {
14     private String message;
15     public ErrorResponse(String message) {
16         this.message = message;
17     }
18     public String getMessage() {
19         return message;
20     }
21 }

If you want to see more advance usage of @ControllerAdvice in Spring 4 read
this post.

Testing the app

As a unit test geek I should have created Unit Tests first. But… I just wanted to test a new tool: Postman (Chrome extension). So I did:

Get accounts (not authorized):

gna1

Post account (does not require authentication:

gna2

Get accounts (authorized):

gna3

Get current account (authorized):

gna4

We are done

That’s it for now. Hope you enjoyed as I enjoyed creating the project. The project and this post took me about ~3h in overall. Most of the time I spent on figuring out the security configuration (I wanted it to be fully in Java) and writing this walkthrough.

分享到:
评论

相关推荐

    开源项目-wal-g-wal-g.zip

    - **命令行接口**:wal-g提供了一系列命令行选项,如`wal-g backup-push`用于创建备份,`wal-g backup-fetch`用于恢复备份,`wal-g wal-fetch`用于获取WAL段等。 4. **wal-g与其他工具的比较** - **对比pg_dump/...

    leveldb实现解析

    - get:查询键对应的值。 - delete:删除键值对。 - snapshot:创建数据库快照。 - NewIterator:创建新的迭代器实例。 - compact:压缩操作,优化存储空间。 设计/实现中的优化: - LevelDB通过使用WAL保证数据的...

    docker-wal-e-replica:使用 WAL-E 创建数据库的 docker 副本

    Postgres WAL-E 副本 这个 docker 镜像结合了 WAL-E 和 Postgres 9.3 来创建一个能够从 WAL-E 备份中恢复的 Docker 镜像。 这可以用于几件事: 开发人员可以使用它从备份创建本地副本 它可用于测试备份的完整性 它...

    Bsure-api-wal:bsure链钱包的服务器api

    Bsure-api-wal server api of wallet for dagx chain Rpc规范参考 1.1. 钱包rpc功能接口 钱包rpc功能接口地址测试URL: 可以以post方式发送json串,参数如下: {"jsonrpc":"2.0","id":1,"method":"getinfo","params...

    wal-e:Postgres的连续归档

    **wal-e:Postgres的连续归档** 在PostgreSQL数据库管理中,数据安全性和高可用性是至关重要的。为了确保数据的完整性,Postgres提供了一种称为“Write-Ahead Log”(WAL)的日志记录机制,它允许在发生故障时进行...

    wal-g:Postgres的存档和恢复

    WAL-G是WAL-E的继任者,但有许多关键区别。 WAL-G对Postgres使用LZ4,LZMA或Brotli压缩,多个处理器和非排他的基本备份。 有关WAL-G设计和实现的更多信息,请参见Citus Data博客文章 Citus 。 目录 安装 可以在“下...

    wal-e-cookbook:用于安装 wal-e 的厨师食谱(postgres 将 WAL 文件连续归档到 s3)

    用于安装 wal-e 的厨师食谱(postgres 连续归档到 s3) 这个秘籍自动化了这篇优秀文章中的许多步骤: : 查看示例用法: 要求 本说明书仅在 ubuntu 服务器上开发和测试 包裹 postgresql - 可选择使用包含 ...

Global site tag (gtag.js) - Google Analytics