内容简介:use spring-security protect ResourceServer
來補一下怎麼保護 ResourceServer
套件依賴
buildscript {
ext {
springBootVersion = '1.5.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
jar {
baseName = 'ps-account'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-data-rest')
compile('org.springframework.boot:spring-boot-starter-mail')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
compile 'org.springframework.security.oauth:spring-security-oauth2:2.0.12.RELEASE'
compile 'org.springframework.security:spring-security-jwt:1.0.7.RELEASE'
compile 'org.modelmapper:modelmapper:0.7.7'
compile 'org.apache.commons:commons-lang3:3.5'
compile 'mysql:mysql-connector-java:6.0.5'
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.6.1'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.6.1'
compile group: 'io.springfox', name: 'springfox-data-rest', version: '2.6.1'
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
spring-security-oauth2 spring-security-jw 是我們需另外加進來的
我們會有一些基礎的帳號或是角色的物件
帳號
@Data
@Entity
@EntityListeners(AuditingEntityListener.class) //加這行 CreatedBy 才會生效
public class Account {
@Id
//@GeneratedValue
private String accountid;
@NotNull
@UniqueUsername(message = "Username already exists")
@Size(min = 5, max = 255, message = "Username have to be grater than 8 characters")
@Column(unique = true)
private String username;
private String email;
@JsonIgnore
@NotNull
@Size(min = 8, max = 255, message = "Password have to be grater than 8 characters")
private String password;
@NotNull
private boolean enabled = true;
@NotNull
private boolean credentialsexpired = false;
@NotNull
private boolean expired = false;
@NotNull
private boolean locked = false;
// @Formula("(select * from Role r where r.roleid in (select roleid from account_role ar where ar.accountid = accountid ))")
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "AccountRole", joinColumns = @JoinColumn(name = "accountid", referencedColumnName = "accountid", insertable = false, updatable = false),
inverseJoinColumns = @JoinColumn(name = "roleid", referencedColumnName = "roleid", insertable = false, updatable = false) )
private List<Role> roles;
@CreatedDate
@Column(name = "createddate")
private Date createddate;
@CreatedBy
@Column(name = "createdby")
private String createdby;
@LastModifiedDate
@Column(name = "lastmodifieddate")
private Date lastmodifieddate;
@LastModifiedBy
@Column(name = "lastmodifiedby")
private String lastmodifiedby;
}
角色
@Data
@Entity
@EntityListeners(AuditingEntityListener.class) //加這行 CreatedBy 才會生效
public class Role {
@Id
private String roleid;
@NotNull
private String code;
@NotNull
private String label;
@CreatedDate
@Column(name = "createddate")
private Date createddate;
@CreatedBy
@Column(name = "createdby")
private String createdby;
@LastModifiedDate
@Column(name = "lastmodifieddate")
private Date lastmodifieddate;
@LastModifiedBy
@Column(name = "lastmodifiedby")
private String lastmodifiedby;
}
配置 ResourceServer Security
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* Created by samchu on 2017/2/17.
*/
@Configuration
@EnableResourceServer
// prePostEnabled 很重要,可以讓你配置 @PreAuthorize("#oauth2.hasScope('account')") 在任何需要的方法上
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
// 這邊配置這服務的 resourceId ,當 jwt 中不含符合的 resourceId 則會拒絕操作
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("account");
resources.tokenServices(tokenServices());
}
// 配置哪些資源可以不用檢驗 Token ,這邊是對 swagger 的檔案無條件存取 及忘記密碼 passwordforget,其他的都要
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.httpBasic().disable();
http.authorizeRequests().antMatchers(
"/swagger-ui.html",
"/v2/api-docs/**",
"/swagger-resources/**",
"/webjars/**",
"/api/v1/passwordforget"
).permitAll();
// 或是也可以集中配置在這裡
//.antMatchers(HttpMethod.GET, "/my").access("#oauth2.hasScope('my-resource.read')")
http.authorizeRequests().anyRequest().fullyAuthenticated();
}
// 主要是配置 JWT 的密鑰
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("ASDFASFsdfsdfsdfsfadsf234asdfasfdas");
return converter;
}
// 配置 JwtTokenStore
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
// 配置,使用預設的 DefaultTokenServices 就可以了,因為我們這邊只是做驗證而已
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
}
接下來就可以在 Service 這邊配置需要的權限
import com.ps.dto.RoleDto;
import com.ps.model.Role;
import com.ps.repository.RoleRepository;
import org.apache.commons.lang3.RandomStringUtils;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Created by samchu on 2017/2/17.
*/
@Service
public class RoleService {
@Autowired
private RoleRepository roleRepository;
// 這邊需要有 role 或是 role.readonly 的操作範圍的人才可以讀取角色列表
@PreAuthorize("#oauth2.hasScope('role') or #oauth2.hasScope('role.readonly')")
public List<Role> listAll(){
return roleRepository.findAll();
}
// 這邊是寫入角色,所以限定 role 的操作範圍才可以寫入
@PreAuthorize("#oauth2.hasScope('role')")
public Role create(RoleDto roleDto) {
ModelMapper modelMapper = new ModelMapper();
Role role = modelMapper.map(roleDto, Role.class);
role.setRoleid(RandomStringUtils.randomAlphanumeric(10));
roleRepository.save(role);
return role;
}
}
像是 hasScope('role') 或是 hasScope('role.readonly') 其實都是非常簡單好讀的敘述方式
那像用到 spring-boot-starter-data-rest 的功能,你其實可以像下面這樣設定
import com.ps.model.Account;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.security.access.prepost.PreAuthorize;
/**
* Created by samchu on 2017/2/9.
*/
@RepositoryRestResource
public interface AccountRepository extends JpaRepository<Account, String> {
//@RestResource(path = "findByUsername")
@Query("SELECT a FROM Account a WHERE a.username = :username")
Account findByUsername(@Param("username") String username);
// Prevents GET /accounts/:id
@Override
@RestResource(exported = true)
@PreAuthorize("#oauth2.hasScope('account')")
Account findOne(String id);
// Prevents GET /account
@Override
@RestResource(exported = false)
@PreAuthorize("#oauth2.hasScope('account')")
Page<Account> findAll(Pageable pageable);
// Prevents POST /account and PATCH /account/:id
@Override
@RestResource(exported = true)
@PreAuthorize("#oauth2.hasScope('account')")
Account save(Account s);
// Prevents DELETE /account/:id
@Override
@RestResource(exported = false)
@PreAuthorize("#oauth2.hasScope('account')")
void delete(Account t);
}
- 透過 RestResource(exported = false) 來配置此方法是否供外部透過 Rest 操作
- 特過 PreAuthorize 一樣可以設定符合操作的 scope 範圍
RestResource 預設是 exported = true,所以有的時候你自己會卡到外部內部權限問題
舉例說外部 rest 操作必須要有 account 操作範圍,但是某些時候是系統內部要操作,這時候你就沒有 token 來檢驗
不過你可以另外設定個系統操作的 Repository 像下面,這樣一來這個 Class 內部所有的方法都不對外露出,就可以放心用啦
@Repository
@RestResource(exported = false)
public interface AccountPrivateRepository extends JpaRepository<Account, String> {
@Query("SELECT a FROM Account a WHERE a.username = :username")
Account findByUsername(@Param("username") String username);
}
啟動程式
@EnableJpaAuditing
//@EnableTransactionManagement
@SpringBootApplication
public class PsAccountApplication {
public static void main(String[] args) {
SpringApplication.run(PsAccountApplication.class, args);
}
}
透過標準的 OAuth 跟 JWT 是不是省下很多自己開發的力氣呢?
使用 OAuth 不論是自用或開放兩相宜XD,使用 JWT 簡化驗證架構跟流程
雖然學習是有成本的,但是比起自己做一套授權系統,應該還是划算的很多
把力氣留在你的商業平台吧,這種基礎建設就盡量使用開源以及標準吧
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
智能家居:商业模式+案例分析+应用实战
陈国嘉 / 人民邮电出版社 / 2016-4 / 49.80元
作为万物互联的关键一环,智能家居的出现和普及已经势不可当,以移动互联网为核心的新技术正在重构智能家居。只有成为智能家居行业的先行者,才能抢占“风口”。 《智能家居:商业模式+案例分析+应用实战》紧扣“智能家居”,从3个方面进行专业、深层次的讲解。首要方面是基础篇,从智能家居的发展现状、产业链、商业分析、抢占入口等方面进行阐述,让读者对智能家居有个初步的认识;第二个方面是技术篇,从智能家居的控......一起来看看 《智能家居:商业模式+案例分析+应用实战》 这本书的介绍吧!