filter java oauth_Spring-Security-Oauth整合Spring-Security,拦截器

程序的目的主要是,在自己开发的web项目中,即提供前端页面调用访问得接口(带有安全机制),也提供第三方调用的API(基于授权认证的).

在整合的过程中发现SpringSecurity不能到即处理自己的web请求也处理第三方调用请求。所以采用拦截器拦截处理本地的web请求,spring-security-oauth对第三方认证请求进行认证与授权。如果对Oauth2.0不熟悉请参考Oauth2.0介绍,程序主要演示password模式和client模式。

官方样例:

1.pom.xml

4.0.0

springboot

testSpringBoot

0.0.1-SNAPSHOT

jar

18_SpringBoot_codeStandard

http://maven.apache.org

UTF-8

org.springframework.boot

spring-boot-starter-parent

1.5.2.RELEASE

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-jdbc

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.2.0

mysql

mysql-connector-java

com.alibaba

druid

1.0.25

org.apache.commons

commons-lang3

3.2

org.springframework.boot

spring-boot-starter-freemarker

org.springframework.boot

spring-boot-starter-aop

com.alibaba

fastjson

1.2.44

org.springframework.boot

spring-boot-starter-data-redis

org.springframework.boot

spring-boot-starter-security

org.springframework.security.oauth

spring-security-oauth2

org.springframework.boot

spring-boot-starter-test

junit

junit

test

org.springframework.boot

spring-boot-maven-plugin

true

maven-compiler-plugin

1.8

1.8

org.apache.tomcat.maven

tomcat7-maven-plugin

2.2

2.application.properties中增加redis配置

#设置session超时时间

server.session.timeout=2000spring.redis.host=127.0.0.1spring.redis.port=6379#配置oauth2过滤的优先级

security.oauth2.resource.filter-order=3

3.第三方调用API

packagecom.niugang.controller;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.context.SecurityContextHolder;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.PathVariable;importorg.springframework.web.bind.annotation.RestController;

@RestControllerpublic classOauthController {

@GetMapping("/api/product/{id}")publicString getProduct(@PathVariable String id) {//for debug

Authentication authentication =SecurityContextHolder.getContext().getAuthentication();return "product id : " +id;

}

@GetMapping("/api/order/{id}")publicString getOrder(@PathVariable String id) {//for debug

Authentication authentication =SecurityContextHolder.getContext().getAuthentication();return "order id : " +id;

}

}

AuthExceptionEntryPoint.java 自定义token授权失败返回信息

packagecom.niugang.exception;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.web.AuthenticationEntryPoint;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.util.Date;importjava.util.HashMap;importjava.util.Map;/*** 自定义AuthExceptionEntryPoint用于tokan校验失败返回信息

*

*@authorniugang

**/

public class AuthExceptionEntryPoint implementsAuthenticationEntryPoint {

@Overridepublic voidcommence(HttpServletRequest request, HttpServletResponse response,

AuthenticationException authException)throwsServletException {

Map map = new HashMap<>();//401 未授权

map.put("error", "401");

map.put("message", authException.getMessage());

map.put("path", request.getServletPath());

map.put("timestamp", String.valueOf(newDate().getTime()));

response.setContentType("application/json");

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);try{

ObjectMapper mapper= newObjectMapper();

mapper.writeValue(response.getOutputStream(), map);

}catch(Exception e) {throw newServletException();

}

}

}

CustomAccessDeniedHandler.java 自定义token授权失败返回信息

packagecom.niugang.exception;importjava.io.IOException;importjava.util.Date;importjava.util.HashMap;importjava.util.Map;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.access.AccessDeniedException;importorg.springframework.security.web.access.AccessDeniedHandler;importorg.springframework.stereotype.Component;importcom.fasterxml.jackson.databind.ObjectMapper;

@Componentpublic class CustomAccessDeniedHandler implementsAccessDeniedHandler {

@AutowiredprivateObjectMapper objectMapper;

@Overridepublic voidhandle(HttpServletRequest request, HttpServletResponse response,

AccessDeniedException accessDeniedException)throwsIOException, ServletException {

response.setContentType("application/json;charset=UTF-8");

Map map = new HashMap<>();

map.put("error", "403");

map.put("message", accessDeniedException.getMessage());

map.put("path", request.getServletPath());

map.put("timestamp", String.valueOf(newDate().getTime()));

response.setContentType("application/json");

response.setStatus(HttpServletResponse.SC_FORBIDDEN);

response.getWriter().write(objectMapper.writeValueAsString(map));

}

}

以下为password模式通过用户名和密码获取token失败,自定义错误信息。

CustomOauthException.java

packagecom.niugang.exception;importcom.fasterxml.jackson.databind.annotation.JsonSerialize;importorg.springframework.security.oauth2.common.exceptions.OAuth2Exception;/***

* @ClassName: CustomOauthException

* @Description:password模式错误处理,自定义登录失败异常信息

*@author: niugang

* @date: 2018年9月5日 下午9:44:38

* @Copyright: 863263957@qq.com. All rights reserved.

**/@JsonSerialize(using= CustomOauthExceptionSerializer.class)public class CustomOauthException extendsOAuth2Exception {publicCustomOauthException(String msg) {super(msg);

}

}

CustomOauthExceptionSerializer.java

packagecom.niugang.exception;importcom.fasterxml.jackson.core.JsonGenerator;importcom.fasterxml.jackson.databind.SerializerProvider;importcom.fasterxml.jackson.databind.ser.std.StdSerializer;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;importjava.io.IOException;importjava.util.Date;importjava.util.Map;/***

* @ClassName: CustomOauthExceptionSerializer

* @Description:password模式错误处理,自定义登录失败异常信息

*@author: niugang

* @date: 2018年9月5日 下午9:45:03

* @Copyright: 863263957@qq.com. All rights reserved.

**/

public class CustomOauthExceptionSerializer extends StdSerializer{private static final long serialVersionUID = 1478842053473472921L;publicCustomOauthExceptionSerializer() {super(CustomOauthException.class);

}

@Overridepublic void serialize(CustomOauthException value, JsonGenerator gen, SerializerProvider provider) throwsIOException {

HttpServletRequest request=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

gen.writeStartObject();

gen.writeStringField("error", String.valueOf(value.getHttpErrorCode()));

gen.writeStringField("message", value.getMessage());//gen.writeStringField("message", "用户名或密码错误");

gen.writeStringField("path", request.getServletPath());

gen.writeStringField("timestamp", String.valueOf(newDate().getTime()));if (value.getAdditionalInformation()!=null) {for (Map.Entryentry :

value.getAdditionalInformation().entrySet()) {

String key=entry.getKey();

String add=entry.getValue();

gen.writeStringField(key, add);

}

}

gen.writeEndObject();

}

}

CustomWebResponseExceptionTranslator.java

packagecom.niugang.exception;importorg.springframework.http.ResponseEntity;importorg.springframework.security.oauth2.common.exceptions.OAuth2Exception;importorg.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;importorg.springframework.stereotype.Component;/***

* @ClassName: CustomWebResponseExceptionTranslator

* @Description:password模式错误处理,自定义登录失败异常信息

*@author: niugang

* @date: 2018年9月5日 下午9:46:36

* @Copyright: 863263957@qq.com. All rights reserved.

**/@Componentpublic class CustomWebResponseExceptionTranslator implementsWebResponseExceptionTranslator {

@Overridepublic ResponseEntity translate(Exception e) throwsException {

OAuth2Exception oAuth2Exception=(OAuth2Exception) e;returnResponseEntity

.status(oAuth2Exception.getHttpErrorCode())

.body(newCustomOauthException(oAuth2Exception.getMessage()));

}

}

4.配置授权认证服务器

packagecom.niugang.config;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.core.userdetails.UserDetailsService;importcom.niugang.exception.AuthExceptionEntryPoint;importorg.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;importorg.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;importorg.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;importorg.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;importorg.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;importorg.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@Configuration/** 在当前应用程序上下文中启用授权服务器(即AuthorizationEndpoint和TokenEndpoint)的便利注释,

* 它必须是一个DispatcherServlet上下文。服务器的许多特性可以通过使用AuthorizationServerConfigurer类型的@

* bean来定制(例如,通过扩展AuthorizationServerConfigurerAdapter)。用户负责使用正常的Spring安全特性(

* @EnableWebSecurity等)来保护授权端点(/oauth/授权),但是令牌端点(/oauth/

* Token)将通过客户端凭证上的HTTP基本身份验证自动获得。

* 客户端必须通过一个或多个AuthorizationServerConfigurers提供一个ClientDetailsService来注册。*/@EnableAuthorizationServerpublic class AuthorizationServerConfiguration extendsAuthorizationServerConfigurerAdapter {//模拟第三方调用api

private static final String DEMO_RESOURCE_ID = "api";

@Autowired

AuthenticationManager authenticationManager;

@Autowired

RedisConnectionFactory redisConnectionFactory;

@AutowiredprivateUserDetailsService userDetailsService;

@AutowiredprivateWebResponseExceptionTranslator customWebResponseExceptionTranslator;/**accessTokenValiditySeconds:设置token无效时间,秒

* refreshTokenValiditySeconds:设置refresh_token无效时间秒*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throwsException {//配置两个客户端,一个用于password认证一个用于client认证

clients.inMemory().withClient("client_1")//基于客户端认证的

.resourceIds(DEMO_RESOURCE_ID)

.authorizedGrantTypes("client_credentials", "refresh_token")

.scopes("select")

.authorities("client")

.secret("123456")/*.refreshTokenValiditySeconds(3600).accessTokenValiditySeconds(60)*/.and().withClient("client_2")//基于密码的

.resourceIds(DEMO_RESOURCE_ID)

.authorizedGrantTypes("password", "refresh_token")

.scopes("select")

.authorities("client")

.secret("123456")/*.refreshTokenValiditySeconds(3600).accessTokenValiditySeconds(60)*/;

}

@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throwsException {

endpoints

.tokenStore(newRedisTokenStore(redisConnectionFactory))

.authenticationManager(authenticationManager)

.userDetailsService(userDetailsService);//密码模式需要在数据库中进行认证

endpoints.exceptionTranslator(customWebResponseExceptionTranslator);//错误异常

}

@Overridepublic void configure(AuthorizationServerSecurityConfigurer oauthServer) throwsException {//允许表单认证

oauthServer.allowFormAuthenticationForClients();

oauthServer.authenticationEntryPoint(newAuthExceptionEntryPoint());

}

}

5.配置资源服务器

packagecom.niugang.config;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.http.SessionCreationPolicy;importorg.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;importorg.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;importorg.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;importcom.niugang.exception.AuthExceptionEntryPoint;importcom.niugang.exception.CustomAccessDeniedHandler;/*** 配置资源服务器

*

*@authorniugang

**/@Configuration

@EnableResourceServer/*** 为OAuth2资源服务器提供方便的注释,使Spring security过滤器能够通过传入的OAuth2令牌验证请求。用户应该添加这个注释,

* 并提供一个名为ResourceServerConfigurer的@Bean(例如,通过ResourceServerConfigurerAdapter),

* 它指定了资源的详细信息(URL路径和资源id)。为了使用这个过滤器,您必须在您的应用程序中的某个地方使用@EnableWebSecurity,

* 或者在您使用这个注释的地方,或者在其他地方。

*

*

**/

public class ResourceServerConfiguration extendsResourceServerConfigurerAdapter{private static final String DEMO_RESOURCE_ID = "api";

@AutowiredprivateCustomAccessDeniedHandler customAccessDeniedHandler;

@Overridepublic voidconfigure(ResourceServerSecurityConfigurer resources) {//resourceId:指定可访问的资源id//stateless:标记,以指示在这些资源上只允许基于标记的身份验证。

resources.resourceId(DEMO_RESOURCE_ID).stateless(true);

resources.authenticationEntryPoint(newAuthExceptionEntryPoint());

resources.accessDeniedHandler(customAccessDeniedHandler);

}

@Overridepublic void configure(HttpSecurity http) throwsException {

http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)

.and()

.authorizeRequests()

.antMatchers("/api/**").authenticated();//配置api访问控制,必须认证过后才可以访问

}

}

6.配置springsecurity

packagecom.niugang.config;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration

@EnableWebSecurity/*** 虽然和oauth认证优先级,起了冲突但是启动也会放置不安全的攻击

*@authorniugang

**/

public class SecurityConfiguration extendsWebSecurityConfigurerAdapter {

@Overrideprotected void configure(HttpSecurity http) throwsException {

http

.authorizeRequests()

.antMatchers("/oauth/**").permitAll();

}

}

7.增加拦截器

packagecom.niugang.interceptor;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.servlet.ModelAndView;public class LogInterceptor implementsHandlerInterceptor {private static Logger logger = LoggerFactory.getLogger(LogInterceptor.class);/*** 执行拦截器之前*/@Overridepublic booleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throwsException {

logger.info("interceptor....在执行前...url:{}", request.getRequestURL());

String user= (String)request.getSession().getAttribute("user");if(user==null){

response.sendRedirect("/myweb/login");

}return true; //返回false将不会执行了

}/*** 调用完处理器,渲染视图之前*/@Overridepublic voidpostHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

ModelAndView modelAndView)throwsException {

logger.info("interceptor.......url:{}", request.getRequestURL());

}

@Overridepublic voidafterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throwsException {

}

}

8.配置拦截器

packagecom.niugang.config;importjava.util.concurrent.TimeUnit;importorg.springframework.context.annotation.Configuration;importorg.springframework.http.CacheControl;importorg.springframework.web.servlet.config.annotation.EnableWebMvc;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration

@EnableWebMvcpublic class MvcConfig extendsWebMvcConfigurerAdapter {/*** 授权拦截的路径 addPathPatterns:拦截的路径 excludePathPatterns:不拦截的路径*/@Overridepublic voidaddInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(new com.niugang.interceptor.LogInterceptor()).addPathPatterns("/**").excludePathPatterns("/login/**","/static/*","/api/**");//"/api/**",不拦截第三方调用的api

super.addInterceptors(registry);

}/*** 修改springboot中默认的静态文件路径*/@Overridepublic voidaddResourceHandlers(ResourceHandlerRegistry registry) {//addResourceHandler请求路径//addResourceLocations 在项目中的资源路径//setCacheControl 设置静态资源缓存时间

registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/")

.setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());super.addResourceHandlers(registry);

}

}

9.配置配置springsecurity数据库认证

packagecom.niugang.service;importjava.util.ArrayList;importjava.util.List;importjavax.annotation.Resource;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.stereotype.Service;importcom.niugang.bean.UserQuery;importcom.niugang.entity.User;importcom.niugang.exception.CheckException;/*** 授权认证业务类

*

*@authorniugang UserDetailsService spring security包里面的

* 重写loadUserByUsername方法

**/@Servicepublic class UserDetailsServiceImpl implementsUserDetailsService {//UserService自定义的,从数据查询信息

@ResourceprivateUserService userService;public UserDetails loadUserByUsername(String username) throwsUsernameNotFoundException {

UserQuery user= newUserQuery();

user.setName(username);//查询用户是否存在

List queryList =userService.queryListByPage(user);if (queryList != null & queryList.size() == 1) {//查询用户拥有的角色

List list = new ArrayList();//如果是admin用户登录,授予SUPERADMIN权限

if(username.equals("admin")){

list.add(new SimpleGrantedAuthority("SUPERADMIN"));

}

org.springframework.security.core.userdetails.User authUser= neworg.springframework.security.core.userdetails.User(

queryList.get(0).getName(), queryList.get(0).getPassword(), list);returnauthUser;

}return null;

}

}

如访问:http:://localhost:8080/myweb/index,没有登录就会跳转到登录页面通过以上配置,对于所有web请求,如果没有登录都会跳转到登录页面,拦截器不会拦截调用api的请求。

访问http:://localhost:8080/myweb/api/order/1会提示没有权限需要认证,默认错误与我们自定义返回信息不一致,并且描述信息较少。那么如何自定义Spring Security Oauth2异常信息,上面也已经有代码实现

(默认的)

(自定义的)

获取token

进行如上配置之后,启动springboot应用就可以发现多了一些自动创建的endpoints(项目启动的时候也会打印mappings):

{[/oauth/authorize]}

{[/oauth/authorize],methods=[POST]

{[/oauth/token],methods=[GET]}

{[/oauth/token],methods=[POST]}

{[/oauth/check_token]}

{[/oauth/error]

通过单元测试,获取client模式的token

packagecom.niugang;importjava.util.HashMap;importorg.junit.runner.RunWith;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.http.ResponseEntity;importorg.springframework.test.context.junit4.SpringRunner;importorg.springframework.web.client.RestTemplate;

@RunWith(SpringRunner.class)

@SpringBootTestpublic classTest {

@org.junit.Testpublic voidqueryToken() {

RestTemplate restTemplate= newRestTemplate();

HashMap hashMap = new HashMap<>();

hashMap.put("grant_type", "client_credentials");

hashMap.put("scope", "select");

hashMap.put("client_id", "client_1");

hashMap.put("client_secret", "123456");

ResponseEntity postForEntity = restTemplate.postForEntity("http://localhost:8080/myweb/oauth/token?grant_type={grant_type}&scope={scope}&client_id={client_id}&client_secret={client_secret}", String.class,

String.class, hashMap);

String body=postForEntity.getBody();

System.out.println(body);

}

}

{"access_token":"5bf8c55d-874d-41fc-94bc-01e2cb8f7142","token_type":"bearer","expires_in":43199,"scope":"select"}

expires_in:访问令牌数秒内的生命周期。例如,值“3600”表示访问令牌将在响应生成后一小时内过期

密码模式也是一样就是放说需要的参数变了

注意此列中的密码模式是基于数据认证的,所以获取token之前确保数据库有对应的username和password

/*** 密码模式*/@org.junit.Testpublic voidqueryToken2() {

RestTemplate restTemplate= newRestTemplate();

HashMap hashMap = new HashMap<>();

hashMap.put("username", "haha");

hashMap.put("password", "123456");

hashMap.put("grant_type", "password");

hashMap.put("scope", "select");

hashMap.put("client_id", "client_2");

hashMap.put("client_secret", "123456");

ResponseEntity postForEntity =restTemplate.postForEntity("http://localhost:8080/myweb/oauth/token?username={username}&password= {password}&grant_type={grant_type}&scope={scope}&client_id={client_id}&client_secret= {client_secret}",

String.class, String.class, hashMap);

String body=postForEntity.getBody();

System.out.println(body);

}

{"access_token":"39aa6302-6614-4b94-8553-a96d9ba0f893","token_type":"bearer","refresh_token":"7f2f41dd-4406-4df4-997a-d80178431db8","expires_in":43199,"scope":"select"}   //密码模式返回了refresh_token

微信公众号

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值