java编程spring boot security oauth2 jwt完美整合例子

来源:互联网 发布:网络交友的危害 编辑:程序博客网 时间:2024/05/16 10:29

一、本文简介

本文主要讲解Java编程中spring boot框架+spring security框架+spring security oauth2框架整合的例子,并且oauth2整合使用jwt方式存储

二、学习前提

首先是讲解oauth2的基本说明:
推荐查看:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

上面的地址可以基本了解下oauth2的原理
oauth2授权图
JWT的认知和基本使用:
推荐查看:
1.什么是JWT? 
2.Java编程中java-jwt框架使用

三、代码编辑

开始代码:
认证服务:
几个核心的配置类:
AuthorizationServerConfiguration.java
package com.leftso.config.security;import java.util.HashMap;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.core.userdetails.User;import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;import org.springframework.security.oauth2.common.OAuth2AccessToken;import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;import org.springframework.security.oauth2.provider.OAuth2Authentication;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;/** * 认证授权服务端 *  * @author leftso * */@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {@Value("${resource.id:spring-boot-application}") // 默认值spring-boot-applicationprivate String resourceId;@Value("${access_token.validity_period:3600}") // 默认值3600int accessTokenValiditySeconds = 3600;@Autowiredprivate AuthenticationManager authenticationManager;@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(this.authenticationManager);endpoints.accessTokenConverter(accessTokenConverter());endpoints.tokenStore(tokenStore());}@Overridepublic void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')");oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("normal-app").authorizedGrantTypes("authorization_code", "implicit").authorities("ROLE_CLIENT").scopes("read", "write").resourceIds(resourceId).accessTokenValiditySeconds(accessTokenValiditySeconds).and().withClient("trusted-app").authorizedGrantTypes("client_credentials", "password").authorities("ROLE_TRUSTED_CLIENT").scopes("read", "write").resourceIds(resourceId).accessTokenValiditySeconds(accessTokenValiditySeconds).secret("secret");}/** * token converter *  * @return */@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {/*** * 重写增强token方法,用于自定义一些token返回的信息 */@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {String userName = authentication.getUserAuthentication().getName();User user = (User) authentication.getUserAuthentication().getPrincipal();// 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration}/** 自定义一些token属性 ***/final Map<String, Object> additionalInformation = new HashMap<>();additionalInformation.put("userName", userName);additionalInformation.put("roles", user.getAuthorities());((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);return enhancedToken;}};accessTokenConverter.setSigningKey("123");// 测试用,资源服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式return accessTokenConverter;}/** * token store *  * @param accessTokenConverter * @return */@Beanpublic TokenStore tokenStore() {TokenStore tokenStore = new JwtTokenStore(accessTokenConverter());return tokenStore;}}
SecurityConfiguration.java
package com.leftso.config.security;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import com.leftso.entity.Account;import com.leftso.repository.AccountRepository;@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)@EnableWebSecuritypublic class SecurityConfiguration extends WebSecurityConfigurerAdapter {// 查询用户使用@AutowiredAccountRepository accountRepository;@Autowiredpublic void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {// auth.inMemoryAuthentication()// .withUser("user").password("password").roles("USER")// .and()// .withUser("app_client").password("nopass").roles("USER")// .and()// .withUser("admin").password("password").roles("ADMIN");//配置用户来源于数据库auth.userDetailsService(userDetailsService());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and().httpBasic().and().csrf().disable();}@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Beanpublic UserDetailsService userDetailsService() {return new UserDetailsService() {@Overridepublic UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {// 通过用户名获取用户信息Account account = accountRepository.findByName(name);if (account != null) {// 创建spring security安全用户User user = new User(account.getName(), account.getPassword(),AuthorityUtils.createAuthorityList(account.getRoles()));return user;} else {throw new UsernameNotFoundException("用户[" + name + "]不存在");}}};}}

受保护的资源服务:
核心配置:
SecurityConfiguration.java
package com.leftso.config;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)@EnableWebSecuritypublic class SecurityConfiguration extends WebSecurityConfigurerAdapter {    @Override    protected void configure(HttpSecurity http) throws Exception {        http                .authorizeRequests()                .antMatchers(HttpMethod.OPTIONS).permitAll()                .anyRequest().authenticated()                .and().httpBasic()                .and().csrf().disable();    }}

ResourceServerConfiguration.java
package com.leftso.config;import javax.servlet.http.HttpServletRequest;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;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.ResourceServerTokenServices;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;import org.springframework.security.web.util.matcher.RequestMatcher;/** * 资源服务端 *  * @author leftso * */@Configuration@EnableResourceServerpublic class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {@Value("${resource.id:spring-boot-application}")private String resourceId;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {// @formatter:offresources.resourceId(resourceId);resources.tokenServices(defaultTokenServices());// @formatter:on}@Overridepublic void configure(HttpSecurity http) throws Exception {// @formatter:offhttp.requestMatcher(new OAuthRequestedMatcher()).authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated();// @formatter:on}private static class OAuthRequestedMatcher implements RequestMatcher {public boolean matches(HttpServletRequest request) {String auth = request.getHeader("Authorization");// Determine if the client request contained an OAuth Authorizationboolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");boolean haveAccessToken = request.getParameter("access_token") != null;return haveOauth2Token || haveAccessToken;}}// ===================================================以下代码与认证服务器一致=========================================/** * token存储,这里使用jwt方式存储 *  * @param accessTokenConverter * @return */@Beanpublic TokenStore tokenStore() {TokenStore tokenStore = new JwtTokenStore(accessTokenConverter());return tokenStore;}/** * Token转换器必须与认证服务一致 *  * @return */@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {///***// * 重写增强token方法,用于自定义一些token返回的信息// *///@Override//public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {//String userName = authentication.getUserAuthentication().getName();//User user = (User) authentication.getUserAuthentication().getPrincipal();// 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration}///** 自定义一些token属性 ***///final Map<String, Object> additionalInformation = new HashMap<>();//additionalInformation.put("userName", userName);//additionalInformation.put("roles", user.getAuthorities());//((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);//OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);//return enhancedToken;//}};accessTokenConverter.setSigningKey("123");// 测试用,授权服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式return accessTokenConverter;}/** * 创建一个默认的资源服务token *  * @return */@Beanpublic ResourceServerTokenServices defaultTokenServices() {final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();defaultTokenServices.setTokenEnhancer(accessTokenConverter());defaultTokenServices.setTokenStore(tokenStore());return defaultTokenServices;}// ===================================================以上代码与认证服务器一致=========================================}

项目源码下载:
https://github.com/leftso/demo-spring-boot-security-oauth2

四、测试上面的编码

测试过程
步骤一:打开浏览器,输入地址
http://localhost:8080/oauth/authorize?client_id=normal-app&response_type=code&scope=read&redirect_uri=/resources/user

会提示输入用户名密码,这时候输入用户名leftso,密码111aaa将会出现以下界面
授权页面
点击Authorize将获取一个随机的code,如图:
授权码

 打开工具postmain,输入以下地址获取授权token
localhost:8080/oauth/token?code=r8YBUL&grant_type=authorization_code&client_id=normal-app&redirect_uri=/resources/user
注意:url中的code就是刚才浏览器获取的code值

获取的token信息如下图:
token


这时候拿到token就可以访问受保护的资源信息了,如下
 
localhost:8081//resources/user

首先,直接访问资源,会报错401如图:
401

我们加上前面获取的access token再试:
localhost:8081//resources/user?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3ByaW5nLWJvb3QtYXBwbGljYXRpb24iXSwidXNlcl9uYW1lIjoibGVmdHNvIiwic2NvcGUiOlsicmVhZCJdLCJyb2xlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn1dLCJleHAiOjE0OTEzNTkyMjksInVzZXJOYW1lIjoibGVmdHNvIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjgxNjI5NzQwLTRhZWQtNDM1Yy05MmM3LWZhOWIyODk5NmYzMiIsImNsaWVudF9pZCI6Im5vcm1hbC1hcHAifQ.YhDJkMSlyIN6uPfSFPbfRuufndvylRmuGkrdprUSJIM

这时候我们就能成功获取受保护的资源信息了:
resource

到这里spring boot整合security oauth2 的基本使用已经讲解完毕.

五、扩展思维

留下一些扩展
1.认证服务的客户端信息是存放内存的,实际应用肯定是不会放内存的,考虑数据库,默认有个DataSource的方式,还有一个自己实现clientDetail接口方式
2.jwt这里测试用的最简单的对称加密,实际应用中使用的一般都是RSA非对称加密方式
原创粉丝点击