18.1 The application senario
- Add a new workout record for a user
- Find all the workouts for a user
- Delete a workout
18.2 Configuring Keycloak as an authorization server
docker 安装 Keycloak
docker network create keycloak-networkdocker run --name mysql -d -p 3306:3306 --net keycloak-network -e MYSQL_DATABASE=keycloak -e MYSQL_USER=keycloak -e MYSQL_PASSWORD=keycloak -e MYSQL_ROOT_PASSWORD=keycloak mysql:5.7docker run --name keycloak -d -p 8080:8080 --net keycloak-network -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e DB_USER=keycloak -e DB_PASSWORD=keycloak -e JDBC_PARAMS='useSSL=false' jboss/keycloak
访问:101.132.251.198:8080
进入控制台页面会报错:
解决方案:
注意修改
docker run -d -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD='admin1024' -e KEYCLOAK_BIND_ADDRESS=0.0.0.0 -e KEYCLOAK_ENABLE_TLS=false quay.io/keycloak/keycloak:15.0.2'docker exec -it {contaierID} bashcd /opt/jboss/keycloak/bin./kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin./kcadm.sh update realms/master -s sslRequired=NONE

这个配置提供了token endpoint,authorization endpoint还有grand types列表

进一步配置authorization server:
- 为系统注册一个client
- 定义一个client scope
- 添加users
- 定义user roles以及自定义的访问tokens
18.2.1 Registering a client for our system

18.2.2 Specifying client scopes


18.2.3 Adding users and obtaining access tokens




调用授权服务的/token端点:


发送请求之后:
对这个token解码之后发现:roles以及username都没有,所以接下来应该分配roles给users以及自定义jwt去包含所有的数据
18.2.4 Defining the user roles
创建roles
将roles分配给users
将mary设置为fitnessadmin其他的为fitnessuser
我们还需要添加三个细节到tokens中:
- Roles
- Username
- Audience claim
18.3 Implementing the resource server
依赖:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-data</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
导入sql文件
配置文件:
定义entity
@Entity@Datapublic class Workout {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private int id;private String user;private LocalDateTime start;private LocalDateTime end;private int difficulty;}
定义repository
public interface WorkoutRepository extends JpaRepository<Workout, Integer> {@Query("select w from Workout w where w.user = ?#{authentication.name}")List<Workout> findAllByUser();}
定义service
@Servicepublic class WorkoutService {@Autowiredprivate WorkoutRepository workoutRepository;@PreAuthorize("#workout.user == authentication.name")public void saveWorkout(Workout workout){workoutRepository.save(workout);}public List<Workout> findWorkouts(){return workoutRepository.findAllByUser();}public void deleteWorkout(Integer id){workoutRepository.deleteById(id);}}
定义controller
@RestController@RequestMapping("/workout")public class WorkoutController {@Autowiredprivate WorkoutService workoutService;@PostMapping("/")public void add(@RequestBody Workout workout){workoutService.saveWorkout(workout);}@GetMapping("/")public List<Workout> findAll(){return workoutService.findWorkouts();}@DeleteMapping("/{id}")public void delete(@PathVariable Integer id){workoutService.deleteWorkout(id);}}
添加两个自定义配置:
定义配置类:
@Configuration@EnableResourceServer@EnableGlobalMethodSecurity(prePostEnabled = true)public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@Value("${claim.aud}")private String claimAud;@Value("${jwkSetUri}")private String urlJwk;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.tokenStore(tokenStore());resources.resourceId(claimAud);}@Beanpublic TokenStore tokenStore() {return new JwkTokenStore(urlJwk);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().mvcMatchers(HttpMethod.DELETE, "/**").hasAnyAuthority("fitnessadmin").anyRequest().authenticated();}@Beanpublic SecurityEvaluationContextExtension securityEvaluationContextExtension(){return new SecurityEvaluationContextExtension();}}
18.4 Testing the application
18.4.1 Proving an authenticated user can only add a record for themself
{"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJsQk9DX2VaSTdiTklzVVRadEszTlpaMDUybnpOQUFMb25XMmZ4aFpTb0JZIn0.eyJleHAiOjE2NTM3OTgwNzMsImlhdCI6MTY1Mzc5ODAxMywianRpIjoiYzQ1ZDkwZjctNmE4Mi00NmRjLWJjZjUtMzZiYjM4YTMyMjQ1IiwiaXNzIjoiaHR0cDovLzEwMS4xMzIuMjUxLjE5ODo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6ImZpdG5lc3NhcHAiLCJzdWIiOiJlOGNlNjkwMC0yMTY5LTRlOTctOGJlOC1hZTI1NGNkNjliNDAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmaXRuZXNzYXBwIiwic2Vzc2lvbl9zdGF0ZSI6IjQxMWRiN2M0LTUzMDMtNDNjZS04NmY1LWE1M2NhNWNkMDZiNCIsImFjciI6IjEiLCJzY29wZSI6ImZpdG5lc3NhcHAiLCJzaWQiOiI0MTFkYjdjNC01MzAzLTQzY2UtODZmNS1hNTNjYTVjZDA2YjQiLCJ1c2VyX25hbWUiOiJiaWxsIiwiYXV0aG9yaXRpZXMiOlsiZml0bmVzc3VzZXIiXX0.HEG7B7ICjeSjIixTU4jgx6r4V6jfLFU7l2cXEQIiVlTB1Q8j3ForbIY7QbkBb6w-yp1GbirBUTsfF7RDgK1vHWEe8sgVHIsjWPXcwLIh9DHG63b3h9sbPmPqGU53ZIuxZSwOzhrHn_y9reS0weskoU5fvhWk3YP84Te4vWCxtPVAJ650NG49ZzsND5pMWKQQmLfjm-apB9h9wZW8Zy9eH9aHOjwQvCqvdTaDM6dZu2AHnYwuCYZJe8Pslgyle7rIHJJzgXqguFeiDXBNDCNirTLtuiosaMmZuihhtN_DgXc6UcWE-RLlDAeteT5eoZq7r2eMDBPwu4GT8RN0tBaGfw","expires_in": 60,"refresh_expires_in": 1800,"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkNjVhYzIwOC1lMTVhLTRhMGItOWNmMy1hNmExZTA1MDBiOGUifQ.eyJleHAiOjE2NTM3OTk4MTMsImlhdCI6MTY1Mzc5ODAxMywianRpIjoiYjBiYTM1ZTItZDc3MC00YTRjLWI2MGQtNTgwNjdkZGRjYmY1IiwiaXNzIjoiaHR0cDovLzEwMS4xMzIuMjUxLjE5ODo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6Imh0dHA6Ly8xMDEuMTMyLjI1MS4xOTg6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiJlOGNlNjkwMC0yMTY5LTRlOTctOGJlOC1hZTI1NGNkNjliNDAiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiZml0bmVzc2FwcCIsInNlc3Npb25fc3RhdGUiOiI0MTFkYjdjNC01MzAzLTQzY2UtODZmNS1hNTNjYTVjZDA2YjQiLCJzY29wZSI6ImZpdG5lc3NhcHAiLCJzaWQiOiI0MTFkYjdjNC01MzAzLTQzY2UtODZmNS1hNTNjYTVjZDA2YjQifQ.JMildS3qX1hV2P7jGVr96Zkj3mDL5AcVYAs7PKEEn9M","token_type": "Bearer","not-before-policy": 0,"session_state": "411db7c4-5303-43ce-86f5-a53ca5cd06b4","scope": "fitnessapp"}
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJsQk9DX2VaSTdiTklzVVRadEszTlpaMDUybnpOQUFMb25XMmZ4aFpTb0JZIn0.eyJleHAiOjE2NTM4MDA5NDQsImlhdCI6MTY1MzgwMDg4NCwianRpIjoiM2U5MDQyNGMtMDY4MC00Njg0LTgxNmMtMDFmMjQwMGY0ZTMxIiwiaXNzIjoiaHR0cDovLzEwMS4xMzIuMjUxLjE5ODo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6ImZpdG5lc3NhcHAiLCJzdWIiOiJlOGNlNjkwMC0yMTY5LTRlOTctOGJlOC1hZTI1NGNkNjliNDAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmaXRuZXNzYXBwIiwic2Vzc2lvbl9zdGF0ZSI6IjI3YjY4MjAzLWIwNDYtNDc5ZS05YmZhLWE4NDllZTAxN2Y4ZCIsImFjciI6IjEiLCJzY29wZSI6ImZpdG5lc3NhcHAiLCJzaWQiOiIyN2I2ODIwMy1iMDQ2LTQ3OWUtOWJmYS1hODQ5ZWUwMTdmOGQiLCJ1c2VyX25hbWUiOiJiaWxsIiwiYXV0aG9yaXRpZXMiOlsiZml0bmVzc3VzZXIiXX0.hesAxNDtMcSsbaeCsF430TMRJ8CAlQnjftnG1WA1tt6_RfVVqXr0v8j1y3vPX9ytWYhxmZ8IVphYp2VbpLog1G0ORMroAFPiqqAScX-GmVdPBPF6dkPRLAGUnRpjxwYIxtia-FQIFbSZYP8o-Bg0ZysADlvgzVa6xKMT246NbOQJbAtcE4N8z-jgXX5JtrTqTltuWzXoGDY_P-g-mrPCpsq0bgnSMmzUflK5epACrTpaEvT4hZ_ylODCO5Iq2Ri3fZFtQkzllpDcxgSweuJfSqrRKYr-Ha3xZyiUUplMyNwqamH0CHpREX0Hr2DsUQDAr06j7J_em0ATZqubIQ-RCA



