#Secures APIsgit
We have configured Spring Security in before posts.angularjs
In this post, I will show you using Spring Security to protect APIs, aka provides Anthentication and Anthorization service for this sample application.github
##Authenticationweb
In Spring security, it is easy to configure JAAS compatible authentication strategy, such as FORM, BASIC, X509 Certiciate etc.spring
Unlike JAAS in which the authentication management is very dependent on the container itself. Spring Security provides some extension points(such as UserDetails
, UserDetailsService
, Authority
) and allows developers to customize and implement the authentication and authorization in a progammatic approach.api
Motioned in before posts, the simplest way to configure Spring security is using AuthenticationManagerBuilder
to build essential required resources.spring-mvc
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .passwordEncoder(passwordEncoder()) .withUser("admin").password("test123").authorities("ROLE_ADMIN") .and() .withUser("test").password("test123").authorities("ROLE_USER"); }
An in-memory database and a HTTP BASIC authentication is easy to prototype applications, as showing as above codes.restful
If you want to store users into your database, firstly create a custom UserDetailsService
bean and implement the findByUsername
method and return a UserDetails
object.mvc
public class SimpleUserDetailsServiceImpl implements UserDetailsService { private static final Logger log = LoggerFactory.getLogger(SimpleUserDetailsServiceImpl.class); private UserRepository userRepository; public SimpleUserDetailsServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("username not found:" + username); } log.debug("found by username @" + username); return user; } }
User
class implements UserDetails
.app
[@Data](http://my.oschina.net/difrik) [@Builder](http://my.oschina.net/u/1245189) @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "users") public class User implements UserDetails, Serializable { /** * */ private static final long serialVersionUID = 1L; @Id() @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Column(name = "username") private String username; @Column(name = "password") private String password; @Column(name = "name") private String name; @Column(name = "email") private String email; @Column(name = "role") private String role; @Column(name = "created_date") @CreatedDate private LocalDateTime createdDate; public String getName() { if (this.name == null || this.name.trim().length() == 0) { return this.username; } return name; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return Arrays.asList(new SimpleGrantedAuthority("ROLE_" + this.role)); } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
Then configure AuthenticationManager
with custom UserDetailsService
instead of inMemoryAuthentication
.
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(new SimpleUserDetailsServiceImpl(userRepository)) .passwordEncoder(passwordEncoder); }
If you want to design a customized Authentication strategy, you could have to create a custom AuthenticationEntryPoint
and AuthenticationProvider
for it. We will discuss this later.
##Anthorization
Once user is authenticated, when he tries to access some resources, such as URL, or execute some methods, it should check if the resource is protected, or has granted permissions on executing the methods.
For REST APIs, the API resources are identified by URI, it is easy to grant authorizations via URL.
Override the configure(HttpSecurity)
of WebSecurityConfigurerAdapter
.
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/api/ping") .permitAll() .and() .authorizeRequests() .antMatchers("/api/**") .authenticated() //.... }
The access control is filter by the Matcher
, there are two built-in matchers, Apache Ant path matcher, and perl like regex matchers. The later is a little complex, but more powerful.
http...antMatchers("/api/**").authenticated()
means all resource URLs match '/api/**' need a valid authentication.
http...antMatchers(HttpMethod.POST, "/api/posts").hasRoles("ADMIN")
indicates only users that have been granted ADMIN role have permission to create a new post.
Combined with resource URLs and HTTP methods, it follows rest convention exactly.
In the a real world application, you can centralize the URL pattern, HTTP Method, and granted ROLES into a certain persistent storage(such as RDBMS or NOSQL) and desgin a friendly web UI to control resource access.
Like JAAS, Spring Security provides several annotations to authorize access on method level.
Firstly you should add @EnableGlobalMethodSecurity
on @Configuration
class to enable it.
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { }
If prePostEnabled is true, the @PreAuthorized
and @PostAuthorized
can be used, it accept Spring EL string and evaluate the result.
For example, only ADMIN user can save post.
@PreAuthorized("hasRole('ADMIN')") public void savePost(Post post){}
And only the post owner can update the post.
@PreAuthorized("#post.author.id==principal.id") public void update(Post post){}
jsr250Enabled provides Java common anntotation compatibility, and allow you use JAAS annotations in Spring project.
securedEnabled enables the legacy @Secured
annotation which does not accept Spring EL as property value.
@Secured("ROLE_USER") public void savePost(Post post){}
Spring provides APIs to fetch current principal info.
For example, get current Authetication from SecurityContextHolder.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
And you can also inject current authenticated Principal like this.
publc List<Post> getByCurrentUser(@AuthenticationPrincipal Principal principal){}
After got the security principal info, you can control the authorizations in codes.
##Source Code
Check out sample codes from my github account.
git clone https://github.com/hantsy/angularjs-springmvc-sample
Or the Spring Boot version:
git clone https://github.com/hantsy/angularjs-springmvc-sample-boot
Read the live version of thess posts from Gitbook:Building RESTful APIs with Spring MVC.