1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        springboot +security+oauth2.0 簡(jiǎn)單教程

        共 29914字,需瀏覽 60分鐘

         ·

        2020-10-26 18:49

        點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

        優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

        ? 作者?|??程序飼養(yǎng)員的自我修養(yǎng)

        來源 |? urlify.cn/Jf63En

        66套java從入門到精通實(shí)戰(zhàn)課程分享

        1、Oauth2簡(jiǎn)介

          OAuth(開放授權(quán))是一個(gè)開放標(biāo)準(zhǔn),允許用戶授權(quán)第三方移動(dòng)應(yīng)用訪問他們存儲(chǔ)在另外的服務(wù)提供者上的信息,而不需要將用戶名和密碼提供給第三方移動(dòng)應(yīng)用或分享他們數(shù)據(jù)的所有內(nèi)容,OAuth2.0是OAuth協(xié)議的延續(xù)版本,但不向后兼容OAuth 1.0即完全廢止了OAuth1.0。

        2、Oauth2服務(wù)器

        • 授權(quán)服務(wù)器 Authorization Service.

        • 資源服務(wù)器 Resource Service.

        ?授權(quán)服務(wù)器

          授權(quán)服務(wù)器,即服務(wù)提供商專門用來處理認(rèn)證的服務(wù)器。在這里簡(jiǎn)單說一下,主要的功能;

          1、通過請(qǐng)求獲得令牌(Token),默認(rèn)的URL是/oauth/token.

        ?  2、根據(jù)令牌(Token)獲取相應(yīng)的權(quán)限.

        資源服務(wù)器

          資源服務(wù)器托管了受保護(hù)的用戶賬號(hào)信息,并且對(duì)接口資源進(jìn)行用戶權(quán)限分配及管理,簡(jiǎn)單的說,就是某個(gè)接口(/user/add),我限制只能持有管理員權(quán)限的用戶才能訪問,那么普通用戶就沒有訪問的權(quán)限。

        ?

        以下摘自百度百科圖:

        ?

        3、Demo實(shí)戰(zhàn)加代碼詳解

        ? ? ??前面我是簡(jiǎn)單地介紹了一下oauth2的一些基本概念,關(guān)于oauth2的深入介紹,可以去搜索更多其它相關(guān)oauth2的博文,在這里推薦一篇前輩的博文https://www.cnblogs.com/Wddpct/p/8976480.html,里面有詳細(xì)的oauth2介紹,包括原理、實(shí)現(xiàn)流程等都講得比較詳細(xì)。我的課題,是主要是以實(shí)戰(zhàn)為主,理論的東西我不想介紹太多, 這里是我個(gè)人去根據(jù)自己的業(yè)務(wù)需求去改造的,存在很多可優(yōu)化的點(diǎn),希望大家可以指出和給予我一些寶貴意見。

          接下來開始介紹我的代碼流程吧! 

        準(zhǔn)備

        ?新建一個(gè)springboot項(xiàng)目,引入以下依賴。


        ????????
        ????????????org.springframework.boot
        ????????????spring-boot-starter
        ????????

        ????????
        ????????????org.springframework.boot
        ????????????spring-boot-starter-test
        ????????????test
        ????????


        ????????
        ????????
        ????????????org.springframework.boot
        ????????????spring-boot-starter-web
        ????????


        ????????
        ???????
        ????????????org.springframework.boot
        ????????????spring-boot-starter-data-redis
        ????????


        ????????
        ????????
        ????????????org.projectlombok
        ????????????lombok
        ????????


        ????????
        ????????
        ????????????org.springframework.boot
        ????????????spring-boot-starter-security
        ????????

        ????????
        ????????
        ????????????org.springframework.security.oauth
        ????????????spring-security-oauth2
        ????????????2.3.3.RELEASE
        ????????


        ????????
        ????????
        ????????????mysql
        ????????????mysql-connector-java
        ????????????5.1.47
        ????????????runtime
        ????????

        ????????
        ????????????org.springframework.boot
        ????????????spring-boot-starter-data-jpa
        ????????


        ????????
        ????????
        ????????????com.alibaba
        ????????????fastjson
        ????????????1.2.47
        ????????

        ????

        項(xiàng)目目錄結(jié)構(gòu)

        ?

        ?

        ?

        接口

        這里我只編寫了一個(gè)AuthController,里面基本所有關(guān)于用戶管理及登錄、注銷的接口我都定義出來了。

        AuthController代碼如下:

        package?com.unionman.springbootsecurityauth2.controller;

        import?com.unionman.springbootsecurityauth2.dto.LoginUserDTO;
        import?com.unionman.springbootsecurityauth2.dto.UserDTO;
        import?com.unionman.springbootsecurityauth2.service.RoleService;
        import?com.unionman.springbootsecurityauth2.service.UserService;
        import?com.unionman.springbootsecurityauth2.utils.AssertUtils;
        import?com.unionman.springbootsecurityauth2.vo.ResponseVO;
        import?lombok.extern.slf4j.Slf4j;
        import?org.springframework.beans.factory.annotation.Autowired;
        import?org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
        import?org.springframework.validation.annotation.Validated;
        import?org.springframework.web.bind.annotation.*;

        import?javax.validation.Valid;

        /**
        ?*?@description?用戶權(quán)限管理
        ?*?@author?Zhifeng.Zeng
        ?*?@date?2019/4/19?13:58
        ?*/
        @Slf4j
        @Validated
        @RestController
        @RequestMapping("/auth/")
        public?class?AuthController?{

        ????@Autowired
        ????private?UserService?userService;

        ????@Autowired
        ????private?RoleService?roleService;

        ????@Autowired
        ????private?RedisTokenStore?redisTokenStore;

        ????/**
        ?????*?@description?添加用戶
        ?????*?@param?userDTO
        ?????*?@return
        ?????*/
        ????@PostMapping("user")
        ????public?ResponseVO?add(@Valid?@RequestBody?UserDTO?userDTO){
        ????????userService.addUser(userDTO);
        ????????return?ResponseVO.success();
        ????}

        ????/**
        ?????*?@description?刪除用戶
        ?????*?@param?id
        ?????*?@return
        ?????*/
        ????@DeleteMapping("user/{id}")
        ????public?ResponseVO?deleteUser(@PathVariable("id")Integer?id){
        ????????userService.deleteUser(id);
        ????????return?ResponseVO.success();
        ????}

        ????/**
        ?????*?@descripiton?修改用戶
        ?????*?@param?userDTO
        ?????*?@return
        ?????*/
        ????@PutMapping("user")
        ????public?ResponseVO?updateUser(@Valid?@RequestBody?UserDTO?userDTO){
        ????????userService.updateUser(userDTO);
        ????????return?ResponseVO.success();
        ????}

        ????/**
        ?????*?@description?獲取用戶列表
        ?????*?@return
        ?????*/
        ????@GetMapping("user")
        ????public?ResponseVO?findAllUser(){
        ????????return?userService.findAllUserVO();
        ????}

        ????/**
        ?????*?@description?用戶登錄
        ?????*?@param?loginUserDTO
        ?????*?@return
        ?????*/
        ????@PostMapping("user/login")
        ????public?ResponseVO?login(LoginUserDTO?loginUserDTO){
        ????????return?userService.login(loginUserDTO);
        ????}


        ????/**
        ?????*?@description?用戶注銷
        ?????*?@param?authorization
        ?????*?@return
        ?????*/
        ????@GetMapping("user/logout")
        ????public?ResponseVO?logout(@RequestHeader("Authorization")?String?authorization){
        ????????redisTokenStore.removeAccessToken(AssertUtils.extracteToken(authorization));
        ????????return?ResponseVO.success();
        ????}

        ????/**
        ?????*?@description?用戶刷新Token
        ?????*?@param?refreshToken
        ?????*?@return
        ?????*/
        ????@GetMapping("user/refresh/{refreshToken}")
        ????public?ResponseVO?refresh(@PathVariable(value?=?"refreshToken")?String?refreshToken){
        ????????return?userService.refreshToken(refreshToken);
        ????}


        ????/**
        ?????*?@description?獲取所有角色列表
        ?????*?@return
        ?????*/
        ????@GetMapping("role")
        ????public?ResponseVO?findAllRole(){
        ????????return?roleService.findAllRoleVO();
        ????}


        }

          這里所有的接口功能,我都已經(jīng)在業(yè)務(wù)代碼里實(shí)現(xiàn)了,后面相關(guān)登錄、注銷、及刷新token的等接口的業(yè)務(wù)實(shí)現(xiàn)的內(nèi)容我會(huì)貼出來。接下來我需要講解的是關(guān)于oath2及security的詳細(xì)配置。

        注意一點(diǎn):這里沒有角色的增刪改功能,只有獲取角色列表功能,為了節(jié)省時(shí)間,我這里的角色列表是項(xiàng)目初始化階段,直接生成的固定的兩個(gè)角色,分別是ROLE_USER(普通用戶)、ROLE_ADMIN(管理員);同時(shí)初始化一個(gè)默認(rèn)的管理員。

        ?

        springbootsecurityauth.sql腳本如下:

        SET?NAMES?utf8;
        SET?FOREIGN_KEY_CHECKS?=?0;
        /**
        初始化角色信息
        ?*/
        ?CREATE?TABLE?IF?NOT?EXISTS?`um_t_role`(
        `id`?INT(11)?PRIMARY?KEY?AUTO_INCREMENT?,
        ?`description`?VARCHAR(255)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NULL?DEFAULT?NULL,
        ?`created_time`?BIGINT(20)?NOT?NULL,
        ?`name`?VARCHAR(255)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NOT?NULL,
        ?`role`?VARCHAR(255)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NOT?NULL
        );
        INSERT?IGNORE?INTO?`um_t_role`(id,`name`,description,created_time,role)?VALUES(1,'管理員','管理員擁有所有接口操作權(quán)限',UNIX_TIMESTAMP(NOW()),'ADMIN'),(2,'普通用戶','普通擁有查看用戶列表與修改密碼權(quán)限,不具備對(duì)用戶增刪改權(quán)限',UNIX_TIMESTAMP(NOW()),'USER');

        /**
        初始化一個(gè)默認(rèn)管理員
        ?*/
        ?CREATE?TABLE?IF?NOT?EXISTS?`um_t_user`(
        `id`?INT(11)?PRIMARY?KEY?AUTO_INCREMENT?,
        ?`account`?VARCHAR(255)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NULL?DEFAULT?NULL,
        ?`description`?VARCHAR(255)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NULL?DEFAULT?NULL,
        ?`password`?VARCHAR(255)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NULL?DEFAULT?NULL,
        ?`name`?VARCHAR(255)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NOT?NULL
        );
        INSERT?IGNORE?INTO?`um_t_user`(id,account,`password`,`name`,description)?VALUES(1,'admin','123456','小小豐','系統(tǒng)默認(rèn)管理員');

        /**
        關(guān)聯(lián)表賦值
        ?*/
        CREATE?TABLE?IF?NOT?EXISTS?`um_t_role_user`(
        `role_id`?INT(11),
        ?`user_id`?INT(11)
        );
        INSERT?IGNORE?INTO?`um_t_role_user`(role_id,user_id)VALUES(1,1);

        配置

        application.yml文件:

        server:
        ??port:?8080
        spring:
        ??#?mysql?配置
        ??datasource:
        ??????url:?jdbc:mysql://localhost:3306/auth_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false
        ??????username:?root
        ??????password:?123456
        ??????schema:?classpath:springbootsecurityauth.sql
        ??????sql-script-encoding:?utf-8
        ??????initialization-mode:?always
        ??????driver-class-name:?com.mysql.jdbc.Driver
        ??????#?初始化大小,最小,最大
        ??????initialSize:?1
        ??????minIdle:?3
        ??????maxActive:?20
        ?????#?配置獲取連接等待超時(shí)的時(shí)間
        ??????maxWait:?60000
        ??????#?配置間隔多久才進(jìn)行一次檢測(cè),檢測(cè)需要關(guān)閉的空閑連接,單位是毫秒
        ??????timeBetweenEvictionRunsMillis:?60000
        ??????#?配置一個(gè)連接在池中最小生存的時(shí)間,單位是毫秒
        ??????minEvictableIdleTimeMillis:?30000
        ??????validationQuery:?select?'x'
        ??????testWhileIdle:?true
        ??????testOnBorrow:?false
        ??????testOnReturn:?false
        ??????#?打開PSCache,并且指定每個(gè)連接上PSCache的大小
        ??????poolPreparedStatements:?true
        ??????maxPoolPreparedStatementPerConnectionSize:?20
        ??????#?配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計(jì),'wall'用于防火墻
        ??????filters:?stat,wall,slf4j
        ??????#?通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
        ??????connectionProperties:?druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        #redis?配置
        ??redis:
        ????open:?true?#?是否開啟redis緩存??true開啟???false關(guān)閉
        ????database:?1
        ????host:?localhost
        ????port:?6379
        ????timeout:?5000s??#?連接超時(shí)時(shí)長(毫秒)
        ????jedis:
        ??????pool:
        ????????max-active:?8?#連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
        ????????max-idle:?8??#連接池中的最大空閑連接
        ????????max-wait:?-1s?#連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
        ????????min-idle:?0??#連接池中的最小空閑連接

        #?jpa?配置
        ??jpa:
        ????database:?mysql
        ????show-sql:?false
        ????hibernate:
        ??????ddl-auto:?update
        ????properties:
        ??????hibernate:
        ????????dialect:?org.hibernate.dialect.MySQL5Dialect

        資源服務(wù)器與授權(quán)服務(wù)器

        編寫類Oauth2Config,實(shí)現(xiàn)資源服務(wù)器與授權(quán)服務(wù)器,這里的資源服務(wù)器與授權(quán)服務(wù)器以內(nèi)部類的形式實(shí)現(xiàn)。

        Oauth2Config代碼如下:

        package?com.unionman.springbootsecurityauth2.config;

        import?com.unionman.springbootsecurityauth2.handler.CustomAuthExceptionHandler;
        import?org.springframework.beans.factory.annotation.Autowired;
        import?org.springframework.context.annotation.Bean;
        import?org.springframework.context.annotation.Configuration;
        import?org.springframework.data.redis.connection.RedisConnectionFactory;
        import?org.springframework.http.HttpMethod;
        import?org.springframework.security.authentication.AuthenticationManager;
        import?org.springframework.security.config.annotation.web.builders.HttpSecurity;
        import?org.springframework.security.config.http.SessionCreationPolicy;
        import?org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
        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.configuration.EnableResourceServer;
        import?org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
        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.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
        import?org.springframework.security.oauth2.provider.token.DefaultTokenServices;
        import?org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

        import?java.util.concurrent.TimeUnit;



        /**
        ?*?@author?Zhifeng.Zeng
        ?*?@description?OAuth2服務(wù)器配置
        ?*/
        @Configuration
        public?class?OAuth2Config?{

        ????public?static?final?String?ROLE_ADMIN?=?"ADMIN";
        ????//訪問客戶端密鑰
        ????public?static?final?String?CLIENT_SECRET?=?"123456";
        ????//訪問客戶端ID
        ????public?static?final?String?CLIENT_ID?="client_1";
        ????//鑒權(quán)模式
        ????public?static?final?String?GRANT_TYPE[]?=?{"password","refresh_token"};

        ????/**
        ?????*?@description?資源服務(wù)器
        ?????*/
        ????@Configuration
        ????@EnableResourceServer
        ????protected?static?class?ResourceServerConfiguration?extends?ResourceServerConfigurerAdapter?{

        ????????@Autowired
        ????????private?CustomAuthExceptionHandler?customAuthExceptionHandler;

        ????????@Override
        ????????public?void?configure(ResourceServerSecurityConfigurer?resources)?{
        ????????????resources.stateless(false)
        ????????????????????.accessDeniedHandler(customAuthExceptionHandler)
        ????????????????????.authenticationEntryPoint(customAuthExceptionHandler);
        ????????}

        ????????@Override
        ????????public?void?configure(HttpSecurity?http)?throws?Exception?{
        ????????????http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
        ????????????????????.and()
        ????????????????????//請(qǐng)求權(quán)限配置
        ????????????????????.authorizeRequests()
        ????????????????????//下邊的路徑放行,不需要經(jīng)過認(rèn)證
        ????????????????????.antMatchers("/oauth/*",?"/auth/user/login").permitAll()
        ????????????????????//OPTIONS請(qǐng)求不需要鑒權(quán)
        ????????????????????.antMatchers(HttpMethod.OPTIONS,?"/**").permitAll()
        ????????????????????//用戶的增刪改接口只允許管理員訪問
        ????????????????????.antMatchers(HttpMethod.POST,?"/auth/user").hasAnyAuthority(ROLE_ADMIN)
        ????????????????????.antMatchers(HttpMethod.PUT,?"/auth/user").hasAnyAuthority(ROLE_ADMIN)
        ????????????????????.antMatchers(HttpMethod.DELETE,?"/auth/user").hasAnyAuthority(ROLE_ADMIN)
        ????????????????????//獲取角色?權(quán)限列表接口只允許系統(tǒng)管理員及高級(jí)用戶訪問
        ????????????????????.antMatchers(HttpMethod.GET,?"/auth/role").hasAnyAuthority(ROLE_ADMIN)
        ????????????????????//其余接口沒有角色限制,但需要經(jīng)過認(rèn)證,只要攜帶token就可以放行
        ????????????????????.anyRequest()
        ????????????????????.authenticated();

        ????????}
        ????}

        ????/**
        ?????*?@description?認(rèn)證授權(quán)服務(wù)器
        ?????*/
        ????@Configuration
        ????@EnableAuthorizationServer
        ????protected?static?class?AuthorizationServerConfiguration?extends?AuthorizationServerConfigurerAdapter?{

        ????????@Autowired
        ????????private?AuthenticationManager?authenticationManager;

        ????????@Autowired
        ????????private?RedisConnectionFactory?connectionFactory;

        ????????@Override
        ????????public?void?configure(ClientDetailsServiceConfigurer?clients)?throws?Exception?{
        ????????????String?finalSecret?=?"{bcrypt}"?+?new?BCryptPasswordEncoder().encode(CLIENT_SECRET);
        ????????????//配置客戶端,使用密碼模式驗(yàn)證鑒權(quán)
        ????????????clients.inMemory()
        ????????????????????.withClient(CLIENT_ID)
        ????????????????????//密碼模式及refresh_token模式
        ????????????????????.authorizedGrantTypes(GRANT_TYPE[0],?GRANT_TYPE[1])
        ????????????????????.scopes("all")
        ????????????????????.secret(finalSecret);
        ????????}

        ????????@Bean
        ????????public?RedisTokenStore?redisTokenStore()?{
        ????????????return?new?RedisTokenStore(connectionFactory);
        ????????}

        ????????/**
        ?????????*?@description?token及用戶信息存儲(chǔ)到redis,當(dāng)然你也可以存儲(chǔ)在當(dāng)前的服務(wù)內(nèi)存,不推薦
        ?????????*?@param?endpoints
        ?????????*/
        ????????@Override
        ????????public?void?configure(AuthorizationServerEndpointsConfigurer?endpoints)?{
        ????????????//token信息存到服務(wù)內(nèi)存
        ????????????/*endpoints.tokenStore(new?InMemoryTokenStore())
        ????????????????????.authenticationManager(authenticationManager);*/

        ????????????//token信息存到redis
        ????????????endpoints.tokenStore(redisTokenStore()).authenticationManager(authenticationManager);
        ????????????//配置TokenService參數(shù)
        ????????????DefaultTokenServices?tokenService?=?new?DefaultTokenServices();
        ????????????tokenService.setTokenStore(endpoints.getTokenStore());
        ????????????tokenService.setSupportRefreshToken(true);
        ????????????tokenService.setClientDetailsService(endpoints.getClientDetailsService());
        ????????????tokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
        ????????????//1小時(shí)
        ????????????tokenService.setAccessTokenValiditySeconds((int)?TimeUnit.HOURS.toSeconds(1));
        ????????????//1小時(shí)
        ????????????tokenService.setRefreshTokenValiditySeconds((int)?TimeUnit.HOURS.toSeconds(1));
        ????????????tokenService.setReuseRefreshToken(false);
        ????????????endpoints.tokenServices(tokenService);
        ????????}

        ????????@Override
        ????????public?void?configure(AuthorizationServerSecurityConfigurer?oauthServer)?{
        ????????????//允許表單認(rèn)證
        ????????????oauthServer.allowFormAuthenticationForClients().tokenKeyAccess("isAuthenticated()")
        ????????????????????.checkTokenAccess("permitAll()");
        ????????}
        ????}
        }

          這里有個(gè)點(diǎn)要強(qiáng)調(diào)一下,就是上面的CustomAuthExceptionHandler ,這是一個(gè)自定義返回異常處理。要知道oauth2在登錄時(shí)用戶密碼不正確或者權(quán)限不足時(shí),oauth2內(nèi)部攜帶的Endpoint處理,會(huì)默認(rèn)返回401并且攜帶的message是它內(nèi)部默認(rèn)的英文,例如像這種:

        ?

        ?

        感覺就很不友好,所以我這里自己去處理AuthException并返回自己想要的數(shù)據(jù)及數(shù)據(jù)格式給客戶端。?

        ?

        CustomAuthExceptionHandler代碼如下:

        package?com.unionman.humancar.handler;

        import?com.alibaba.fastjson.JSON;
        import?com.unionman.humancar.enums.ResponseEnum;
        import?com.unionman.humancar.vo.ResponseVO;
        import?lombok.extern.slf4j.Slf4j;
        import?org.springframework.security.access.AccessDeniedException;
        import?org.springframework.security.core.AuthenticationException;
        import?org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
        import?org.springframework.security.web.AuthenticationEntryPoint;
        import?org.springframework.security.web.access.AccessDeniedHandler;
        import?org.springframework.stereotype.Component;

        import?javax.servlet.ServletException;
        import?javax.servlet.http.HttpServletRequest;
        import?javax.servlet.http.HttpServletResponse;
        import?java.io.IOException;

        /**
        ?*?@author?Zhifeng.Zeng
        ?*?@description?自定義未授權(quán)?token無效?權(quán)限不足返回信息處理類
        ?*?@date?2019/3/4?15:49
        ?*/
        @Component
        @Slf4j
        public?class?CustomAuthExceptionHandler?implements?AuthenticationEntryPoint,?AccessDeniedHandler?{
        ????@Override
        ????public?void?commence(HttpServletRequest?request,?HttpServletResponse?response,?AuthenticationException?authException)?throws?IOException,?ServletException?{

        ????????Throwable?cause?=?authException.getCause();
        ????????response.setContentType("application/json;charset=UTF-8");
        ????????response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        ????????//?CORS?"pre-flight"?request
        ????????response.addHeader("Access-Control-Allow-Origin",?"*");
        ????????response.addHeader("Cache-Control","no-cache");
        ????????response.addHeader("Access-Control-Allow-Methods",?"GET,?POST,?PUT,?DELETE,?OPTIONS");
        ????????response.setHeader("Access-Control-Allow-Headers",?"x-requested-with");
        ????????response.addHeader("Access-Control-Max-Age",?"1800");
        ????????if?(cause?instanceof?InvalidTokenException)?{
        ????????????log.error("InvalidTokenException?:?{}",cause.getMessage());
        ????????????//Token無效
        ????????????response.getWriter().write(JSON.toJSONString(ResponseVO.error(ResponseEnum.ACCESS_TOKEN_INVALID)));
        ????????}?else?{
        ????????????log.error("AuthenticationException?:?NoAuthentication");
        ????????????//資源未授權(quán)
        ????????????response.getWriter().write(JSON.toJSONString(ResponseVO.error(ResponseEnum.UNAUTHORIZED)));
        ????????}

        ????}

        ????@Override
        ????public?void?handle(HttpServletRequest?request,?HttpServletResponse?response,?AccessDeniedException?accessDeniedException)?throws?IOException,?ServletException?{
        ????????response.setContentType("application/json;charset=UTF-8");
        ????????response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        ????????response.addHeader("Access-Control-Allow-Origin",?"*");
        ????????response.addHeader("Cache-Control","no-cache");
        ????????response.addHeader("Access-Control-Allow-Methods",?"GET,?POST,?PUT,?DELETE,?OPTIONS");
        ????????response.setHeader("Access-Control-Allow-Headers",?"x-requested-with");
        ????????response.addHeader("Access-Control-Max-Age",?"1800");
        ????????//訪問資源的用戶權(quán)限不足
        ????????log.error("AccessDeniedException?:?{}",accessDeniedException.getMessage());
        ????????response.getWriter().write(JSON.toJSONString(ResponseVO.error(ResponseEnum.INSUFFICIENT_PERMISSIONS)));
        ????}
        }

        Spring Security

          這里security主要承擔(dān)的角色是,用戶資源管理,簡(jiǎn)單地說就是,在客戶端發(fā)送登錄請(qǐng)求的時(shí)候,security會(huì)將先去根據(jù)用戶輸入的用戶名和密碼,去查數(shù)據(jù)庫,如果匹配,那么就把相應(yīng)的用戶信息進(jìn)行一層轉(zhuǎn)換,然后交給認(rèn)證授權(quán)管理器,然后認(rèn)證授權(quán)管理器會(huì)根據(jù)相應(yīng)的用戶,給他分發(fā)一個(gè)token(令牌),然后下次進(jìn)行請(qǐng)求的時(shí)候,攜帶著該token(令牌),認(rèn)證授權(quán)管理器就能根據(jù)該token(令牌)去找到相應(yīng)的用戶了。

        SecurityConfig代碼如下:

        package?com.unionman.springbootsecurityauth2.config;

        import?com.unionman.springbootsecurityauth2.domain.CustomUserDetail;
        import?com.unionman.springbootsecurityauth2.entity.User;
        import?com.unionman.springbootsecurityauth2.repository.UserRepository;
        import?lombok.extern.slf4j.Slf4j;
        import?org.springframework.beans.factory.annotation.Autowired;
        import?org.springframework.context.annotation.Bean;
        import?org.springframework.context.annotation.Configuration;
        import?org.springframework.security.authentication.AuthenticationManager;
        import?org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
        import?org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
        import?org.springframework.security.core.GrantedAuthority;
        import?org.springframework.security.core.authority.AuthorityUtils;
        import?org.springframework.security.core.userdetails.UserDetails;
        import?org.springframework.security.core.userdetails.UserDetailsService;
        import?org.springframework.security.core.userdetails.UsernameNotFoundException;
        import?org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
        import?org.springframework.security.crypto.factory.PasswordEncoderFactories;
        import?org.springframework.security.crypto.password.PasswordEncoder;
        import?org.springframework.web.client.RestTemplate;

        import?java.util.List;

        /**
        ?*?@description?Security核心配置
        ?*?@author?Zhifeng.Zeng
        ?*/
        @Configuration
        @EnableWebSecurity
        @Slf4j
        public?class?SecurityConfig?extends?WebSecurityConfigurerAdapter?{


        ????@Autowired
        ????private?UserRepository?userRepository;

        ????@Bean
        ????@Override
        ????public?AuthenticationManager?authenticationManagerBean()?throws?Exception?{
        ????????return?super.authenticationManagerBean();
        ????}

        ????@Bean
        ????public?RestTemplate?restTemplate(){
        ????????return?new?RestTemplate();
        ????}

        ????@Bean
        ????@Override
        ????protected?UserDetailsService?userDetailsService()?{
        ????????BCryptPasswordEncoder?bCryptPasswordEncoder?=?new?BCryptPasswordEncoder();
        ???????return?new?UserDetailsService(){
        ???????????@Override
        ???????????public?UserDetails?loadUserByUsername(String?username)?throws?UsernameNotFoundException?{
        ???????????????log.info("username:{}",username);
        ???????????????User?user?=?userRepository.findUserByAccount(username);
        ???????????????if(user?!=?null){
        ???????????????????CustomUserDetail?customUserDetail?=?new?CustomUserDetail();
        ???????????????????customUserDetail.setUsername(user.getAccount());
        ???????????????????customUserDetail.setPassword("{bcrypt}"+bCryptPasswordEncoder.encode(user.getPassword()));
        ???????????????????List?list?=?AuthorityUtils.createAuthorityList(user.getRole().getRole());
        ???????????????????customUserDetail.setAuthorities(list);
        ???????????????????return?customUserDetail;
        ???????????????}else?{//返回空
        ???????????????????return?null;
        ???????????????}

        ???????????}
        ???????};
        ????}

        ????@Bean
        ????PasswordEncoder?passwordEncoder()?{
        ????????return?PasswordEncoderFactories.createDelegatingPasswordEncoder();
        ????}
        }

        業(yè)務(wù)邏輯

          這里我只簡(jiǎn)單地實(shí)現(xiàn)了用戶的增刪改查以及用戶登錄的業(yè)務(wù)邏輯。并沒有做太深的業(yè)務(wù)處理,主要是重點(diǎn)看一下登錄的業(yè)務(wù)邏輯。里面引了幾個(gè)組件,簡(jiǎn)單說一下,RestTemplate(http客戶端)用于發(fā)送http請(qǐng)求,ServerConfig(服務(wù)配置)用于獲取本服務(wù)的ip和端口,RedisUtil(redis工具類) 用戶對(duì)redis進(jìn)行緩存的增刪改查操作。

        ?

        UserServiceImpl代碼如下:

        package?com.unionman.springbootsecurityauth2.service.impl;
        ?
        import?com.unionman.springbootsecurityauth2.config.ServerConfig;
        import?com.unionman.springbootsecurityauth2.domain.Token;
        import?com.unionman.springbootsecurityauth2.dto.LoginUserDTO;
        import?com.unionman.springbootsecurityauth2.dto.UserDTO;
        import?com.unionman.springbootsecurityauth2.entity.Role;
        import?com.unionman.springbootsecurityauth2.entity.User;
        import?com.unionman.springbootsecurityauth2.enums.ResponseEnum;
        import?com.unionman.springbootsecurityauth2.enums.UrlEnum;
        import?com.unionman.springbootsecurityauth2.repository.UserRepository;
        import?com.unionman.springbootsecurityauth2.service.RoleService;
        import?com.unionman.springbootsecurityauth2.service.UserService;
        import?com.unionman.springbootsecurityauth2.utils.BeanUtils;
        import?com.unionman.springbootsecurityauth2.utils.RedisUtil;
        import?com.unionman.springbootsecurityauth2.vo.LoginUserVO;
        import?com.unionman.springbootsecurityauth2.vo.ResponseVO;
        import?com.unionman.springbootsecurityauth2.vo.RoleVO;
        import?com.unionman.springbootsecurityauth2.vo.UserVO;
        import?org.springframework.beans.factory.annotation.Autowired;
        import?org.springframework.stereotype.Service;
        import?org.springframework.transaction.annotation.Transactional;
        import?org.springframework.util.LinkedMultiValueMap;
        import?org.springframework.util.MultiValueMap;
        import?org.springframework.web.client.RestClientException;
        import?org.springframework.web.client.RestTemplate;
        ?
        import?java.util.ArrayList;
        import?java.util.List;
        import?java.util.concurrent.TimeUnit;
        ?
        import?static?com.unionman.springbootsecurityauth2.config.OAuth2Config.CLIENT_ID;
        import?static?com.unionman.springbootsecurityauth2.config.OAuth2Config.CLIENT_SECRET;
        import?static?com.unionman.springbootsecurityauth2.config.OAuth2Config.GRANT_TYPE;
        ?
        @Service
        public?class?UserServiceImpl?implements?UserService?{
        ?
        ????@Autowired
        ????private?UserRepository?userRepository;
        ?
        ????@Autowired
        ????private?RoleService?roleService;
        ?
        ????@Autowired
        ????private?RestTemplate?restTemplate;
        ?
        ????@Autowired
        ????private?ServerConfig?serverConfig;
        ?
        ????@Autowired
        ????private?RedisUtil?redisUtil;
        ?
        ????@Override
        ????@Transactional(rollbackFor?=?Exception.class)
        ????public?void?addUser(UserDTO?userDTO)??{
        ????????User?userPO?=?new?User();
        ????????User?userByAccount?=?userRepository.findUserByAccount(userDTO.getAccount());
        ????????if(userByAccount?!=?null){
        ????????????//此處應(yīng)該用自定義異常去返回,在這里我就不去具體實(shí)現(xiàn)了
        ????????????try?{
        ????????????????throw?new?Exception("This?user?already?exists!");
        ????????????}?catch?(Exception?e)?{
        ????????????????e.printStackTrace();
        ????????????}
        ????????}
        ????????userPO.setCreatedTime(System.currentTimeMillis());
        ????????//添加用戶角色信息
        ????????Role?rolePO?=?roleService.findById(userDTO.getRoleId());
        ????????userPO.setRole(rolePO);
        ????????BeanUtils.copyPropertiesIgnoreNull(userDTO,userPO);
        ????????userRepository.save(userPO);
        ????}
        ?
        ????@Override
        ????@Transactional(rollbackFor?=?Exception.class)
        ????public?void?deleteUser(Integer?id)??{
        ????????User?userPO?=?userRepository.findById(id).get();
        ????????if(userPO?==?null){
        ????????????//此處應(yīng)該用自定義異常去返回,在這里我就不去具體實(shí)現(xiàn)了
        ????????????try?{
        ????????????????throw?new?Exception("This?user?not?exists!");
        ????????????}?catch?(Exception?e)?{
        ????????????????e.printStackTrace();
        ????????????}
        ????????}
        ????????userRepository.delete(userPO);
        ????}
        ?
        ????@Override
        ????@Transactional(rollbackFor?=?Exception.class)
        ????public?void?updateUser(UserDTO?userDTO)?{
        ????????User?userPO?=?userRepository.findById(userDTO.getId()).get();
        ????????if(userPO?==?null){
        ????????????//此處應(yīng)該用自定義異常去返回,在這里我就不去具體實(shí)現(xiàn)了
        ????????????try?{
        ????????????????throw?new?Exception("This?user?not?exists!");
        ????????????}?catch?(Exception?e)?{
        ????????????????e.printStackTrace();
        ????????????}
        ????????}
        ????????BeanUtils.copyPropertiesIgnoreNull(userDTO,?userPO);
        ????????//修改用戶角色信息
        ????????Role?rolePO?=?roleService.findById(userDTO.getRoleId());
        ????????userPO.setRole(rolePO);
        ????????userRepository.saveAndFlush(userPO);
        ????}
        ?
        ????@Override
        ????public?ResponseVO>?findAllUserVO()?{
        ????????List?userPOList?=?userRepository.findAll();
        ????????List?userVOList?=?new?ArrayList<>();
        ????????userPOList.forEach(userPO->{
        ????????????UserVO?userVO?=?new?UserVO();
        ????????????BeanUtils.copyPropertiesIgnoreNull(userPO,userVO);
        ????????????RoleVO?roleVO?=?new?RoleVO();
        ????????????BeanUtils.copyPropertiesIgnoreNull(userPO.getRole(),roleVO);
        ????????????userVO.setRole(roleVO);
        ????????????userVOList.add(userVO);
        ????????});
        ????????return?ResponseVO.success(userVOList);
        ????}
        ?
        ????@Override
        ????public?ResponseVO?login(LoginUserDTO?loginUserDTO)?{
        ????????MultiValueMap?paramMap?=?new?LinkedMultiValueMap<>();
        ????????paramMap.add("client_id",?CLIENT_ID);
        ????????paramMap.add("client_secret",?CLIENT_SECRET);
        ????????paramMap.add("username",?loginUserDTO.getAccount());
        ????????paramMap.add("password",?loginUserDTO.getPassword());
        ????????paramMap.add("grant_type",?GRANT_TYPE[0]);
        ????????Token?token?=?null;
        ????????try?{
        ????????????//因?yàn)閛auth2本身自帶的登錄接口是"/oauth/token",并且返回的數(shù)據(jù)類型不能按我們想要的去返回
        ????????????//但是我的業(yè)務(wù)需求是,登錄接口是"user/login",由于我沒研究過要怎么去修改oauth2內(nèi)部的endpoint配置
        ????????????//所以這里我用restTemplate(HTTP客戶端)進(jìn)行一次轉(zhuǎn)發(fā)到oauth2內(nèi)部的登錄接口,比較簡(jiǎn)單粗暴
        ????????????token?=?restTemplate.postForObject(serverConfig.getUrl()?+?UrlEnum.LOGIN_URL.getUrl(),?paramMap,?Token.class);
        ????????????LoginUserVO?loginUserVO?=?redisUtil.get(token.getValue(),?LoginUserVO.class);
        ????????????if(loginUserVO?!=?null){
        ????????????????//登錄的時(shí)候,判斷該用戶是否已經(jīng)登錄過了
        ????????????????//如果redis里面已經(jīng)存在該用戶已經(jīng)登錄過了的信息
        ????????????????//我這邊要刷新一遍token信息,不然,它會(huì)返回上一次還未過時(shí)的token信息給你
        ????????????????//不便于做單點(diǎn)維護(hù)
        ????????????????token?=?oauthRefreshToken(loginUserVO.getRefreshToken());
        ????????????????redisUtil.deleteCache(loginUserVO.getAccessToken());
        ????????????}
        ????????}?catch?(RestClientException?e)?{
        ????????????try?{
        ????????????????e.printStackTrace();
        ????????????????//此處應(yīng)該用自定義異常去返回,在這里我就不去具體實(shí)現(xiàn)了
        ????????????????//throw?new?Exception("username?or?password?error");
        ????????????}?catch?(Exception?e1)?{
        ????????????????e1.printStackTrace();
        ????????????}
        ????????}
        ????????//這里我拿到了登錄成功后返回的token信息之后,我再進(jìn)行一層封裝,最后返回給前端的其實(shí)是LoginUserVO
        ????????LoginUserVO?loginUserVO?=?new?LoginUserVO();
        ????????User?userPO?=?userRepository.findUserByAccount(loginUserDTO.getAccount());
        ????????BeanUtils.copyPropertiesIgnoreNull(userPO,?loginUserVO);
        ????????loginUserVO.setPassword(userPO.getPassword());
        ????????loginUserVO.setAccessToken(token.getValue());
        ????????loginUserVO.setAccessTokenExpiresIn(token.getExpiresIn());
        ????????loginUserVO.setAccessTokenExpiration(token.getExpiration());
        ????????loginUserVO.setExpired(token.isExpired());
        ????????loginUserVO.setScope(token.getScope());
        ????????loginUserVO.setTokenType(token.getTokenType());
        ????????loginUserVO.setRefreshToken(token.getRefreshToken().getValue());
        ????????loginUserVO.setRefreshTokenExpiration(token.getRefreshToken().getExpiration());
        ????????//存儲(chǔ)登錄的用戶
        ????????redisUtil.set(loginUserVO.getAccessToken(),loginUserVO,TimeUnit.HOURS.toSeconds(1));
        ????????return?ResponseVO.success(loginUserVO);
        ????}
        ?
        ????/**
        ?????*?@description?oauth2客戶端刷新token
        ?????*?@param?refreshToken
        ?????*?@date?2019/03/05?14:27:22
        ?????*?@author?Zhifeng.Zeng
        ?????*?@return
        ?????*/
        ????private?Token?oauthRefreshToken(String?refreshToken)?{
        ????????MultiValueMap?paramMap?=?new?LinkedMultiValueMap<>();
        ????????paramMap.add("client_id",?CLIENT_ID);
        ????????paramMap.add("client_secret",?CLIENT_SECRET);
        ????????paramMap.add("refresh_token",?refreshToken);
        ????????paramMap.add("grant_type",?GRANT_TYPE[1]);
        ????????Token?token?=?null;
        ????????try?{
        ????????????token?=?restTemplate.postForObject(serverConfig.getUrl()?+?UrlEnum.LOGIN_URL.getUrl(),?paramMap,?Token.class);
        ????????}?catch?(RestClientException?e)?{
        ????????????try?{
        ????????????????//此處應(yīng)該用自定義異常去返回,在這里我就不去具體實(shí)現(xiàn)了
        ????????????????throw?new?Exception(ResponseEnum.REFRESH_TOKEN_INVALID.getMessage());
        ????????????}?catch?(Exception?e1)?{
        ????????????????e1.printStackTrace();
        ????????????}
        ????????}
        ????????return?token;
        ????}
        ?
        ?
        }

        ?

        示例

          這里我使用postman(接口測(cè)試工具)去對(duì)接口做一些簡(jiǎn)單的測(cè)試。

        (1)這里我去發(fā)送一個(gè)獲取用戶列表的請(qǐng)求:

        ?

        結(jié)果可以看到,由于沒有攜帶token信息,所以返回了如下信息。

        ?

        (2)接下來,我們先去登錄。

        ?

        登錄成功后,這里會(huì)返回一系列信息,記住這個(gè)token信息,待會(huì)我們嘗試使用這個(gè)token信息再次請(qǐng)求上面那個(gè)獲取用戶列表接口。

        ?

        (3)攜帶token去獲取用戶列表

        ?

        ?

        可以看到,可以成功拿到接口返回的資源(用戶的列表信息)啦。

        ?

        (4)這里測(cè)試一下,用戶注銷的接口。用戶注銷,會(huì)把redis里的token信息全部清除。

        ?

        ?

        可以看到,注銷成功了。那么我們?cè)儆眠@個(gè)已經(jīng)被注銷的token再去請(qǐng)求一遍那個(gè)獲取用戶列表接口。

        ?

        很顯然,此時(shí)已經(jīng)報(bào)token無效了。

        ?

          接下來,我們對(duì)角色的資源分配管理進(jìn)行一個(gè)測(cè)試。可以看到我們庫里面,項(xiàng)目初始化的時(shí)候,就已經(jīng)創(chuàng)建了一個(gè)管理員,我們上面配置已經(jīng)規(guī)定,管理員是擁有所有接口的訪問權(quán)限的,而普通用戶卻只有查詢權(quán)限。我們現(xiàn)在就來測(cè)試一下這個(gè)效果。

        (1)首先我使用該管理員去添加一個(gè)普通用戶。

        ?

        可以看到,我們返回了添加成功信息了,那么我去查看一下用戶列表。

        很顯然,現(xiàn)在這個(gè)用戶已經(jīng)成功添加進(jìn)去了。

        ?

        (2)接下來,我們用新添加的用戶去登錄一下該系統(tǒng)。

        ?

        該用戶也登錄成功了,我們先保存這個(gè)token。

        ?

        (3)我們現(xiàn)在攜帶著剛才登錄的普通用戶"小王"的token去添加一個(gè)普通用戶。

        ? ? ? ? ?

        可以看到,由于"小王"是普通用戶,所以是不具備添加用戶的權(quán)限的。

        ?

        (4)那么我們現(xiàn)在用"小王"這個(gè)用戶去查詢一下用戶列表。

        ?

        總結(jié)

          基于Springboot集成security、oauth2實(shí)現(xiàn)認(rèn)證鑒權(quán)、資源管理的博文就到這了。描述得其實(shí)已經(jīng)較為詳細(xì)了,具體代碼的示例也給了相關(guān)的注釋?;旧隙际且宰詈?jiǎn)單最基本的方式去做的一個(gè)整合Demo。一般實(shí)際應(yīng)用場(chǎng)景里,業(yè)務(wù)會(huì)比較復(fù)雜,其中還會(huì)有,修改密碼,重置密碼,主動(dòng)延時(shí)token時(shí)長,加密解密等等。這些就根據(jù)自己的業(yè)務(wù)需求去做相應(yīng)的處理了,基本上的操作都是針對(duì)redis去做,因?yàn)閠oken相關(guān)信息都是存儲(chǔ)在redis的。





        粉絲福利:108本java從入門到大神精選電子書領(lǐng)取

        ???

        ?長按上方鋒哥微信二維碼?2 秒
        備注「1234」即可獲取資料



        感謝點(diǎn)贊支持下哈?

        瀏覽 99
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美一级性爱视频 | 成人影音先锋 | 99久久99久久免费精品不卡 | 嫩草视频在线观看 | 日韩午夜视频在线观看 | 免费视频在线观看黄 | 成人国产精品 | 高清一区二区三区 | 欧美精品成人在线视频 | 亚洲资源网 |