最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot整合SpringSecurity認(rèn)證與授權(quán)

 更新時(shí)間:2023年11月10日 10:41:16   作者:全棧小白.  
在項(xiàng)目開(kāi)發(fā)中,權(quán)限認(rèn)證是很重要的,尤其是一些管理類的系統(tǒng),對(duì)于權(quán)限要求更為嚴(yán)格,本文主要介紹了SpringBoot整合SpringSecurity認(rèn)證與授權(quán),感興趣的可以了解一下

嘮嗑部分

在項(xiàng)目開(kāi)發(fā)中,權(quán)限認(rèn)證是很重要的,尤其是一些管理類的系統(tǒng),對(duì)于權(quán)限要求更為嚴(yán)格,那么在Java開(kāi)發(fā)中,常用的權(quán)限框架有哪些呢?

推薦的有兩種,Shiro 與 SpringSecurity,當(dāng)然也可以結(jié)合切面自己實(shí)現(xiàn)

Shiro是Apache開(kāi)源的一款權(quán)限框架,比較輕量級(jí),簡(jiǎn)單容易學(xué),但是不能在其中注入Spring中的容器Bean

SpringSecurity是Spring生態(tài)中的一個(gè)組件,比較重量級(jí),它也整合了OAuth2協(xié)議,對(duì)于Spring框架來(lái)說(shuō),更推薦SpringSecurity

今天我們就來(lái)分享一下如何整合SpringSecurity進(jìn)行認(rèn)證與授權(quán),順便實(shí)現(xiàn)一下token的無(wú)感知續(xù)期

SpringSecurity進(jìn)行認(rèn)證與授權(quán)是SpringSecurity框架進(jìn)行處理,我們就不必多說(shuō),按照步驟進(jìn)行編碼就OK了

token的無(wú)感知續(xù)期我們來(lái)說(shuō)一下思路:

1、用戶在登錄成功后,由服務(wù)器下發(fā)token,有效期30分鐘。

2、客戶端拿到token之后,請(qǐng)求其余需要認(rèn)證的接口時(shí),再請(qǐng)求頭攜帶token訪問(wèn)。

3、服務(wù)器編寫過(guò)濾器,對(duì)請(qǐng)求頭中的token進(jìn)行驗(yàn)證,判斷用戶登錄是否有效,于此同時(shí),判斷token有效期是否即將過(guò)期,如果即將過(guò)期,重新頒發(fā)token,如果已過(guò)期,返回401未認(rèn)證狀態(tài)碼。

上面說(shuō)到判斷token有效期是否即將過(guò)期,說(shuō)明一下哈

oauth2中是頒發(fā)了兩個(gè)token,一個(gè)access_token(訪問(wèn)token),一個(gè)refresh_token(刷新token),刷新token的有效期是訪問(wèn)token的2倍,如果訪問(wèn)token過(guò)期,就拿刷新token重新申請(qǐng)?jiān)L問(wèn)token

我們只有一個(gè)token,邏輯是判斷token的有效期,如果有效期小于15分鐘,就刷新token,這樣的話既可以實(shí)現(xiàn)toekn的無(wú)感知刷新

實(shí)際的token是比較長(zhǎng)的一段字符串,標(biāo)準(zhǔn)的jwt token包括頭部、載荷、簽名,格式如下

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwiaWQiOiIxIiwiYXV0aGVudGljYXRpb25zIjpbImFkbWluIl0sInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE2ODg2MjI3MjMsImV4cCI6MTY4ODYyNDUyM30.xPRul6ePE1bwSe70rbo0-jPFUxU9O9MPQf9gliZ18X8

接口設(shè)計(jì)說(shuō)明:

1、用戶認(rèn)證處理器

接口地址:/auth/login

請(qǐng)求方式:POST

請(qǐng)求頭:content-type: application/json;charset=utf-8

接口功能說(shuō)明:進(jìn)行用戶認(rèn)證,頒發(fā)token,實(shí)際的token比較長(zhǎng),我們是生成了一個(gè)字符串充當(dāng)token,實(shí)際token存在于redis中

接口參數(shù):

{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->"password": "","username": ""}

出參說(shuō)明:

tokenInfo:token信息,包括token的失效時(shí)間、token值、用戶信息等等

user:用戶信息

{
    "code": 200,
    "data": {
        "tokenInfo": {
            "expirationTime": "2023-07-06 12:25:42",
            "token": "3f49e86b93c743f2865a4446a7a85398",
            "user": {
                "authentications": [
                    "ROLE_user"
                ],
                "id": "2",
                "username": "user"
            }
        },
        "user": {
            "roles": [
                "ROLE_user"
            ],
            "userId": "2",
            "userStatus": 1,
            "userType": 0,
            "username": "admin"
        }
    },
    "msg": "登陸成功"
}

2、用戶令牌檢查處理器

接口地址:/auth/checkToken

請(qǐng)求方式:POST

請(qǐng)求頭: X-Access-Token

接口功能說(shuō)明:token令牌檢查

出參說(shuō)明:

status: 檢查結(jié)果

token:token信息

{
    "code": 200,
    "data": {
        "status": true,
        "token": {
            "expirationTime": "2023-07-06 12:25:42",
            "token": "3f49e86b93c743f2865a4446a7a85398",
            "user": {
                "authentications": [
                    "ROLE_user"
                ],
                "id": "2",
                "username": "user"
            }
        }
    },
    "msg": "操作成功"
}

3、測(cè)試接口

/admin/common/test:只有admin角色才能訪問(wèn)

/common/test:任何角色都可以訪問(wèn)

言歸正傳

1、相關(guān)SQL腳本

create database `springsecurity_case` character set 'utf8mb4';
use `springsecurity_case`;

create table t_user(
    user_id varchar(50) primary key comment '用戶id',
    username varchar(50) not null comment '用戶名',
    password varchar(100) not null comment '密碼',
    role varchar(50) not null comment '角色',
    user_status tinyint(1) default 1 comment '用戶狀態(tài)'
);

insert into t_user values ('1', 'admin', '$2a$10$wmUXgiTZzc3ux3h3UiuxWumeDYbt8uaZmmPw6utx9GyyuGEDSTNJy', 'admin', 1),
                          ('2', 'user', '$2a$10$wmUXgiTZzc3ux3h3UiuxWumeDYbt8uaZmmPw6utx9GyyuGEDSTNJy', 'user', 1);

2、創(chuàng)建項(xiàng)目&導(dǎo)入依賴

<!--  ... 常規(guī)化的依賴省略了-->
<!--        redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--        springSecurity-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

3、UserDetailService實(shí)現(xiàn)類的編寫,認(rèn)證的主邏輯

/*
 * @Project:springboot-springsecurity-case
 * @Author:cxs
 * @Motto:放下雜念,只為迎接明天更好的自己
 * */
@Service
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (!StringUtils.hasLength(username)) {
            throw new UsernameNotFoundException("用戶名豈能為空!");
        }
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        User user = userMapper.selectOne(queryWrapper);

        if (ObjectUtils.isEmpty(user)) {
            throw new UsernameNotFoundException("用戶名不存在");
        }

        if (user.getUserStatus().equals(2)) {
            throw new LockedException("賬戶已被鎖定,認(rèn)證失敗");
        }

        GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + user.getRole());
        Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        grantedAuthorities.add(grantedAuthority);
        return new AuthUser(user.getUserId(), username, user.getPassword(), grantedAuthorities);
    }
}

4、Token過(guò)濾器的編寫

/*
 * @Project:springboot-springsecurity-case
 * @Author:cxs
 * @Motto:放下雜念,只為迎接明天更好的自己
 * */
@Component
public class TokenVerificationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private CommonConfig commonConfig;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // 獲取url,如果在核心配置文件中配置了白名單,則跳過(guò)驗(yàn)證
        String requestURI = request.getRequestURI();
        if (ignore(requestURI)) {
            filterChain.doFilter(request, response);
            return;
        }
        // 獲取X-Access-Token
        String header = request.getHeader(CommonContent.TOKEN);
        if (!StringUtils.hasLength(header)) {
            filterChain.doFilter(request, response);
            return;
        }
        Token token = null;
        List<String> strings = null;
        try {
            // 根據(jù)X-Access-Token去redis查詢真實(shí)token
            String tokenStr = redisUtil.getString(redisUtil.getCacheKey(CachePrefixContent.TOKEN_PREFIX, header.trim()));
            if (!StringUtils.hasLength(tokenStr)) {
                response(response, BaseResult.error().setCode(ResponseStateConstant.NO_LOGIN).setMsg("用戶認(rèn)證信息已過(guò)期"));
                return;
            }
            if (jwtUtil.validTokenIssued(tokenStr)) {
                response(response, BaseResult.error().setCode(ResponseStateConstant.NO_LOGIN).setMsg("用戶認(rèn)證信息已過(guò)期"));
                return;
            }
            // 校驗(yàn)信息是否正確,省略
            token = jwtUtil.parseToken(tokenStr);
            strings = token.getUser().getAuthentications();
            Authentication context = new UsernamePasswordAuthenticationToken(
                    token.getUser().getUsername(),
                    token.getUser().getUsername(),
                    AuthorityUtils.createAuthorityList(strings.toArray(new String[0]))
            );
            SecurityContextHolder.getContext().setAuthentication(context);
            Long expire = redisUtil.getExpire(redisUtil.getCacheKey(CachePrefixContent.TOKEN_PREFIX, header));
            // 有效期小于15分鐘,續(xù)時(shí)
            if (expire <= 900L) {
                redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.TOKEN_PREFIX, header), jwtUtil.generateToken(token.getUser()), commonConfig.getValidityTime(), TimeUnit.MINUTES);
            }
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            if (e instanceof SignatureException) {
                BaseResult error = BaseResult.error();
                error.setMsg(CurrencyErrorEnum.UNAUTHORIZED.getMsg());
                error.setCode(CurrencyErrorEnum.UNAUTHORIZED.getCode());
                response(response, error);
            } else if (e instanceof ExpiredJwtException) {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                BaseResult error = BaseResult.error();
                error.setMsg(CurrencyErrorEnum.UNAUTHORIZED_BE_OVERDUE.getMsg());
                error.setCode(CurrencyErrorEnum.UNAUTHORIZED_BE_OVERDUE.getCode());
                response(response, error);
            } else if (e instanceof JwtException) {
                BaseResult error = BaseResult.error();
                error.setMsg(CurrencyErrorEnum.UNAUTHORIZED.getMsg());
                error.setCode(CurrencyErrorEnum.UNAUTHORIZED.getCode());
                response(response, error);
            } else {
                throw e;
            }
        }
    }
    // ...
}

5、Security核心配置文件編寫

/*
 * @Project:springboot-springsecurity-case
 * @Author:cxs
 * @Motto:放下雜念,只為迎接明天更好的自己
 * */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /*
    創(chuàng)建加密編碼器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private TokenVerificationFilter tokenVerificationFilter;

    @Autowired
    private AccessForbiddenHandler forbiddenHandler;

    @Autowired
    private AuthenticationHandler authenticationHandler;

    @Autowired
    private CommonConfig commonConfig;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 請(qǐng)求授權(quán)管理
        http.authorizeRequests()
                // 其他的請(qǐng)求都需要授權(quán)
                .antMatchers(commonConfig.getIgnoreUrl()).permitAll()
                .antMatchers("/common/**").hasAnyRole("admin", "user")
                .antMatchers("/admin/**").hasRole("admin")
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(forbiddenHandler)
                .authenticationEntryPoint(authenticationHandler)
                .and()
                .csrf().disable()
                // 整合token校驗(yàn)過(guò)濾器
                .addFilterBefore(tokenVerificationFilter, UsernamePasswordAuthenticationFilter.class)
                // 禁用springsecurity的本地存儲(chǔ),做無(wú)狀態(tài)登錄
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    /**
     * 注入認(rèn)證管理器
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}

6、登錄認(rèn)證邏輯

這一塊有兩個(gè)邏輯

1、首先如果用戶帶著X-Access-Token進(jìn)行登錄,首先判斷是否有效,如果有效的話,直接將之前的token回傳給用戶

2、如果失效、或者用戶未帶X-Access-Token,進(jìn)行登錄邏輯

public void login(UserLoginDTO dto, HttpServletRequest request, HttpServletResponse response, BaseResult result) {
    // 判斷用戶是否攜帶X-Access-Token
    String accessTokenKey = request.getHeader(CommonContent.TOKEN);
    if (StringUtils.hasLength(accessTokenKey)) {
        String tokenStr = redisUtil.getString(redisUtil.getCacheKey(CachePrefixContent.TOKEN_PREFIX, accessTokenKey.trim()));
        Token token = null;
        try {
            token = jwtUtil.parseToken(tokenStr);
        } catch (Exception e) {
            log.info("用戶登錄:用戶已有token校驗(yàn)失敗");
        }
        // 進(jìn)行token驗(yàn)證
        if (!ObjectUtils.isEmpty(token)) {
            String generateToken = jwtUtil.generateToken(token.getUser());
            redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.TOKEN_PREFIX, accessTokenKey), generateToken, commonConfig.getValidityTime(), TimeUnit.MINUTES);
            Token parseToken = jwtUtil.parseToken(generateToken);
            if (!ObjectUtils.isEmpty(parseToken)) parseToken.setToken(accessTokenKey);
            UserLoginVO vo = new UserLoginVO();
            vo.setTokenInfo(parseToken);
            UserVO userVO = new UserVO();
            User userInfo = userMapper.selectById(parseToken.getUser().getId());
            BeanUtils.copyProperties(userInfo, userVO);
            userVO.setRoles(parseToken.getUser().getAuthentications());
            vo.setUser(userVO);
            result.setCode(ResponseStateConstant.OPERA_SUCCESS).setData(vo).setMsg("登陸成功");
            return;
        }
    }
    // 如果失效,或者未帶X-Access-Token,進(jìn)行登錄邏輯
    String password = dto.getPassword().trim();
    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(dto.getUsername().trim(), password);
    Authentication authenticate = null;
    try {
        authenticate = authenticationManager.authenticate(token);
    } catch (AuthenticationException e) {
        e.printStackTrace();
        result.setCode(ResponseStateConstant.OPERA_FAIL).setMsg(e.getMessage());
    }
    if (authenticate != null) {
        UserLoginVO vo = new UserLoginVO();
        SecurityContextHolder.getContext().setAuthentication(authenticate);
        Object principal = authenticate.getPrincipal();
        AuthUser user = (AuthUser) principal;
        List<String> auths = CollectionUtils.isEmpty(user.getAuthorities()) ? new ArrayList<>(0) :
        user.getAuthorities().stream().map(a -> a.getAuthority()).collect(Collectors.toList());
        // 用戶登陸成功,生成token
        String tokenStr = IdUtil.simpleUUID();
        String generateToken = jwtUtil.generateToken(UserSubject.builder()
                                                     .id(user.getId())
                                                     .username(user.getUsername())
                                                     .authentications(auths).build());
        redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.TOKEN_PREFIX, tokenStr), generateToken, commonConfig.getValidityTime(), TimeUnit.MINUTES);
        Token parseToken = jwtUtil.parseToken(generateToken);
        if (!ObjectUtils.isEmpty(parseToken)) parseToken.setToken(tokenStr);
        vo.setTokenInfo(parseToken);
        UserVO userVO = new UserVO();
        User userInfo = userMapper.selectById(user.getId());
        BeanUtils.copyProperties(userInfo, userVO);
        userVO.setRoles(auths);
        vo.setUser(userVO);
        result.setCode(ResponseStateConstant.OPERA_SUCCESS).setData(vo).setMsg("登陸成功");
    }
}

7、測(cè)試

使用user角色登錄

image-20230706142601489

使用token分別訪問(wèn)測(cè)試接口

image-20230706143252046

使用admin角色登錄

image-20230706143329522

使用token分別訪問(wèn)測(cè)試接口

image-20230706143457124

token令牌檢查接口測(cè)試

image-20230706143545026

查看真實(shí)token,在redis存儲(chǔ)

image-20230706144019683

到此這篇關(guān)于SpringBoot整合SpringSecurity認(rèn)證與授權(quán)的文章就介紹到這了,更多相關(guān)SpringBoot SpringSecurity認(rèn)證與授權(quán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Springboot框架整合添加redis緩存功能

    Springboot框架整合添加redis緩存功能

    緩存就是一個(gè)存儲(chǔ)器,在技術(shù)選型中,常用?Redis?作為緩存數(shù)據(jù)庫(kù)。緩存主要是在獲取資源方便性能優(yōu)化的關(guān)鍵方面。Redis?是一個(gè)高性能的?key-value?數(shù)據(jù)庫(kù),接下來(lái)通過(guò)本文給大家介紹Springboot框架整合添加redis緩存功能,感興趣的朋友一起看看吧
    2021-11-11
  • Java原生HttpClient的使用詳解

    Java原生HttpClient的使用詳解

    Java開(kāi)發(fā)語(yǔ)言中實(shí)現(xiàn)HTTP請(qǐng)求的方法主要有兩種:一種是JAVA的標(biāo)準(zhǔn)類HttpUrlConnection,比較原生的實(shí)現(xiàn)方法;另一種是第三方開(kāi)源框架HTTPClient。本文就將詳細(xì)講講Java中原生HttpClient的使用,需要的可以參考一下
    2022-04-04
  • 學(xué)習(xí)Java之異常到底該如何捕獲和處理

    學(xué)習(xí)Java之異常到底該如何捕獲和處理

    我們知道,Java的異常處理是通過(guò)5個(gè)關(guān)鍵字來(lái)實(shí)現(xiàn)的,即try、catch、throw、throws和finally,try?catch語(yǔ)句用于捕獲并處理異常,但具體該怎么捕獲異常,怎么拋出異常,什么時(shí)候拋,什么時(shí)候捕,感興趣的小伙伴跟著小編一起來(lái)看看吧
    2023-08-08
  • SpringBoot發(fā)送郵件功能 驗(yàn)證碼5分鐘過(guò)期

    SpringBoot發(fā)送郵件功能 驗(yàn)證碼5分鐘過(guò)期

    這篇文章主要為大家詳細(xì)介紹了SpringBoot發(fā)送郵件功能,驗(yàn)證碼5分鐘過(guò)期,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • 解析Spring Cloud Bus消息總線

    解析Spring Cloud Bus消息總線

    這篇文章主要介紹了Spring Cloud Bus消息總線的介紹及使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-10-10
  • java抓取網(wǎng)頁(yè)或文件中的郵箱號(hào)碼

    java抓取網(wǎng)頁(yè)或文件中的郵箱號(hào)碼

    這篇文章主要為大家詳細(xì)介紹了java如何抓取網(wǎng)頁(yè)或文件中的郵箱號(hào)碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • springboot的異步任務(wù):無(wú)返回值和有返回值問(wèn)題

    springboot的異步任務(wù):無(wú)返回值和有返回值問(wèn)題

    這篇文章主要介紹了springboot的異步任務(wù):無(wú)返回值和有返回值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Java線程阻塞和喚醒的幾種方式詳解

    Java線程阻塞和喚醒的幾種方式詳解

    這篇文章主要介紹了Java線程阻塞和喚醒的幾種方式詳解,線程阻塞是指當(dāng)一個(gè)線程無(wú)法繼續(xù)執(zhí)行時(shí),它會(huì)進(jìn)入阻塞狀態(tài),直到某個(gè)條件滿足后才能繼續(xù)執(zhí)行,線程阻塞可以通過(guò)多種方式實(shí)現(xiàn),如等待鎖、等待IO操作、等待其他線程的完成等,需要的朋友可以參考下
    2023-10-10
  • SpringMVC接收多個(gè)對(duì)象的4種方法

    SpringMVC接收多個(gè)對(duì)象的4種方法

    這篇文章主要為大家詳細(xì)介紹了SpringMVC接收多個(gè)對(duì)象的4種方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • 解讀Map中的computeIfAbsent方法

    解讀Map中的computeIfAbsent方法

    文章介紹了Java 8中Map接口的computeIfAbsent方法,該方法用于在Map中獲取指定鍵的值,如果不存在則調(diào)用提供的函數(shù)計(jì)算值并放入Map中,并給出了使用示例
    2026-05-05

最新評(píng)論

舟山市| 五指山市| 鸡东县| 克山县| 金溪县| 河曲县| 靖西县| 固阳县| 江西省| 翼城县| 尉氏县| 新营市| 甘泉县| 上林县| 新乡县| 北票市| 长治市| 通辽市| 建宁县| 安义县| 泰来县| 聊城市| 讷河市| 永修县| 水城县| 改则县| 太湖县| 白山市| 盈江县| 平阴县| 清镇市| 鄂温| 凤凰县| 武冈市| 冕宁县| 黄陵县| 南平市| 平江县| 芦山县| 怀来县| 精河县|