主頁 >  其他 > SpringSecurity前后端分離,登錄邏輯,基于SpringSecurity實作權限管理系統

SpringSecurity前后端分離,登錄邏輯,基于SpringSecurity實作權限管理系統

2021-12-16 12:33:28 其他

先參考文章https://blog.csdn.net/grd_java/article/details/107584578,了解springSecurity基本操作,另外,資料庫也在這篇文章創建了

文章目錄

  • 一、環境說明(太基本的就不講了,本文主要講如何前后端分離模式整合Security)
  • 二、配置Security
    • 1. 用戶物體類繼承UserDetails介面
    • 2. 撰寫JWT工具類
    • 3. 配置JWT過濾器
    • 4. 自定義回傳結果
    • 5. Security配置類
  • 三、實作登錄邏輯
    • 1. 專門給登錄用戶的物體類
    • 2. controller
    • 3. service
  • 四、通過swagger測驗
    • 1. 配置swagger,攜帶指定請求頭authorization,規定哪些路徑需要認證(只有規定的路徑才會攜帶請求頭)
    • 2. 測驗
  • 五、驗證碼實作
    • 1. 生成驗證碼
    • 2. 修改service邏輯
  • 六、實作權限管理系統

基本效果說明
  1. 請求login介面,獲取token
  2. 獲取token后,通過封裝名為Authorization的請求頭,值為Bearer+空格+token字串的形式請求需要授權介面(就是請求時,需要帶上authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2Mzk0NjA2MTYsImV4cCI6MTYzOTQ2MTMwMCwic3ViIjoiYWRtaW4iLCJjcmVhdGVkIjoxNjM5NDYwNjE2MTI0fQ._oKG2qarbKi84gxdQjHoRSHd3hx-INQn1CscMgAiASW0B6tsPIjWi1LMr35OTtWR-WvQ8R6tRAvkp0Q3RQm0LQ這樣的請求頭)
  1. authorization是請求頭key
  2. Bearer 是我們的一個頭標識
  3. 剩下的就是token字串
原始碼,碼云

@TODO 因為要將springSecurity整個的權限控制流程都涉及到,暫時還沒寫完,寫完會上傳到倉庫

一、環境說明(太基本的就不講了,本文主要講如何前后端分離模式整合Security)

common除了Spring security模塊外,都在這篇文章中有講解https://blog.csdn.net/grd_java/article/details/107452826
在這里插入圖片描述
在這里插入圖片描述
不知道怎么建表,請到文章開頭的鏈接中參考
在這里插入圖片描述
在這里插入圖片描述

二、配置Security

首先,我們在yml中需要配置一些引數,是我們個人規定好的
  1. 我們規定相應的token 請求頭為authorization:Bearer token字串
    在這里插入圖片描述
jwt:
  tokenHeader: Authorization #JWT存盤的請求頭,請求是這個就是token的key
  secret: ukc8BDbRigUDaY6pZFfWus2jZWLPHO #JWT加解密使用的密鑰
  expiration: 684808 #JWT有效時間(60*60*24)
  tokenHead: Bearer #JWT負載中拿到的開頭,token字串,tokenHead是字串的頭,也就是以它開頭

1. 用戶物體類繼承UserDetails介面

在這里插入圖片描述

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DdUser物件,使用Spring Security框架就要繼承UserDetails介面,實作方法,將回傳值改為true", description="")
public class DdUser implements UserDetails {
    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private Integer id;
    private String username;
    private String password;

    /**
     * 所有權限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    /**
     * 賬號是否過期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 賬號是否被鎖定
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 憑證(密碼)是否過期
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否啟用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

2. 撰寫JWT工具類

在這里插入圖片描述

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT工具類
 */
@Component
public class JwtTokenUtil {
    private static final String CLAIM_KEY_USERNAME="sub";//荷載,用戶名的key
    private static final String CLAIM_KEY_CREATED="created";//荷載,創建時間key
    /**
     * 專案中推薦使用此形式的密鑰和過期時間,因為解耦合
     */
    @Value("${jwt.secret}")
    private String secret;//密鑰
    @Value("${jwt.expiration}")
    private Long expiration;//過期時間

    /**
     * 不推薦使用這種,因為寫死到了代碼中
     */
    public static final long EXPIRE = 1000 * 60 * 60 * 24;                      //設定token過期時間
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";   //設定token密鑰,我瞎寫的,每個公司都有按自己規則生成的密鑰

    //生成token字串

    /**
     * 根據荷載生成token字符
     * @param id 密言1
     * @param nickname 密言2
     * @return
     */
    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()                                          //構建jwt字串
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")                             //設定jwt頭資訊

                .setSubject("guli-user")                                           //分類,名字隨便起的,不同的分類可以設定不同的過期
                .setIssuedAt(new Date())                                           //設定過期時間的計時起始值為當前時間
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))      //設定過期時間為當前時間+EXPIRE我們設定的過期時間

                .claim("id", id)                                                //token主體,這里放你需要的資訊,我們實作登陸,就放用戶登陸資訊
                .claim("nickname", nickname)                                    //需要多少主體資訊,就設定多少個claim屬性

                .signWith(SignatureAlgorithm.HS256, APP_SECRET)                    //簽名哈希,根據指定規則和我們的密鑰設定簽名
                .compact();

        return JwtToken;
    }

    /**
     * 根據spring security 物件生成token
     * @param userDetails UserDetails物件,spring security會將用戶資訊保存在這個物件
     * @return
     */
    public String getJwtToken(UserDetails userDetails){

        //分組荷載,也就是主體資訊,我們將用戶名和有效時間封裝到token中
        Map<String,Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED,new Date());

        String JwtToken = gennerateToken(claims);

        return JwtToken;
    }
    private String gennerateToken(Map<String,Object> claims){
        String JwtToken =Jwts.builder()                                          //構建jwt字串
//                .setHeaderParam("typ", "JWT")
//                .setHeaderParam("alg", "HS256")                             //設定jwt頭資訊
//
//                .setSubject("dd-user")                                             //分類,名字隨便起的,不同的分類可以設定不同的過期
                .setIssuedAt(new Date())                                           //設定過期時間的計時起始值為當前時間
                .setExpiration(new Date(System.currentTimeMillis() + expiration))  //設定過期時間為當前時間+EXPIRE我們設定的過期時間
                .addClaims(claims)
//                .claim(claims)                          //token主體,這里放你需要的資訊,我們實作登陸,就放用戶登陸資訊
//                .claim()                               //需要多少主體資訊,就設定多少個claim屬性
                .signWith(SignatureAlgorithm.HS512, secret)                    //簽名哈希,根據指定規則和我們的密鑰設定簽名
                .compact();
        return JwtToken;
    }
    /**
     * 從token中獲取登錄用戶名
     */
    public String getUsernameByToken(String token){
        String username;

        try {
            Claims claims = getClaimsFormToken(token); //獲取主體內容
            //claims.get(CLAIM_KEY_USERNAME);
            username = claims.getSubject();//獲取主體值,上面注釋代碼和這個都可以,獲取的是CLAIM_KEY_USERNAME常量封裝的key,對應的值
        }catch (Exception e){
            username = null;
            e.printStackTrace();
        }
        return username;

    }

    /**
     * 從token中獲取荷載
     * @param token
     * @return
     */
    private Claims getClaimsFormToken(String token){
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();//決議token字串,獲取主體內容
        }catch (Exception e){
            e.printStackTrace();
        }
        return claims;
    }

    /**
     * 從荷載中獲取過期時間
     */
    private Date getExpiredDateFromToken(String token){
        Claims claimsFormToken = getClaimsFormToken(token);
        return claimsFormToken.getExpiration();
    }
    /**
     * 驗證token是否失效,失效回傳true,沒失效回傳false
     */
    private boolean isTokenExpired(String token){
        Date expiredDateFromToken = getExpiredDateFromToken(token);
//        boolean before = expiredDateFromToken.before(new Date());
        return expiredDateFromToken.before(new Date());//如果當前時間再過期時間之前,就是還沒有到過期時間,回傳true,否則回傳false
    }

    /**
     * 判斷token是否有效
     * @param jwtToken token字串
     * @return
     */
    public boolean checkToken(String jwtToken,UserDetails userDetails) {
        if(StringUtils.isEmpty(jwtToken)) return false;//如果為空直接回傳false表示失效
        String username;
        try {
             username = getUsernameByToken(jwtToken);//根據token獲取用戶名
        } catch (Exception e) {
            e.printStackTrace();
            username = null;
            return false;//獲取不到用戶名,直接失效
        }
        if(!username.equals(userDetails.getUsername())) return false;//如果token的用戶名與當前UserDetails用戶名不一致,直接失效
        if(isTokenExpired(jwtToken)) return false;//如果token失效,回傳false
        return true;
    }
    /**
     * 判斷token是否可以被重繪
     */
    public boolean canRefresh(String token){
        return !isTokenExpired(token);//已經過期,表示可以被重繪
    }

    /**
     * 重繪token
     */
    public String refreshToken(String token){
        Claims claimsFormToken = getClaimsFormToken(token);
        claimsFormToken.put(CLAIM_KEY_CREATED,new Date());
        return gennerateToken(claimsFormToken);
    }
}

3. 配置JWT過濾器

任何請求都需要進行過濾,如果請求攜帶我們規定的token,那么將token放在spring Security物件中

在這里插入圖片描述

import com.dd.security.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

/**
 * JWT登錄授權過濾器,繼承OncePerRequestFilter
 */
public class JwtAuthencationTokenFilter extends OncePerRequestFilter {


    @Value("${jwt.tokenHeader}")
    private String tokenHeader;//JWT存盤的請求頭

    @Value("${jwt.tokenHead}")
    private String tokenHead;//JWT負載中拿到的開頭,token字串,tokenHead是字串的頭,也就是以它開頭
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 前置攔截
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
//        Enumeration<String> headerNames = request.getHeaderNames();//debug用的
        //通過tokenHeader獲取驗證頭,也就是tokenHead+token字串
        String authHeader = request.getHeader(tokenHeader);

        //如果驗證頭存在,并且token字串符合我們規定的tokenHead開頭
        if(authHeader!=null && authHeader.startsWith(tokenHead)){
            //將token截取出來
            String authToken = authHeader.substring(tokenHead.length());
            //根據token獲取用戶名
            String username = jwtTokenUtil.getUsernameByToken(authToken);
            //如果token存在,但是Security全域背景關系沒有用戶,表示用戶沒有登錄
            if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
                //登錄
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                //如果token有效
                if(jwtTokenUtil.checkToken(authToken,userDetails)){
                    //更新登錄用戶物件UserDetails
                    UsernamePasswordAuthenticationToken authenticationToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    //重新設定到用戶物件中
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    //設定到security全域背景關系
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }

        filterChain.doFilter(request,response);//放行
    }
}

4. 自定義回傳結果

當訪問介面沒有權限,自定義回傳結果

在這里插入圖片描述

import com.dd.common_utils.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
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;
import java.io.PrintWriter;

/**
 * 當訪問介面沒有權限,自定義回傳結果
 */
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");//設定回應編碼
        response.setContentType("application/json");//設定回應格式,前后端分離最好是json
        PrintWriter out = response.getWriter();//獲取response輸出流
        //創建回傳結果物件
        Result result = Result.error().code(403).message("權限不足,請聯系管理員");
        //以json字串形式放到response輸出流中
        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();//重繪
        out.close();//關閉流
    }
}
當未登錄或token失效時訪問介面,自定義回傳結果

在這里插入圖片描述

import com.dd.common_utils.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

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

/**
 * 當未登錄或token失效時訪問介面,自定義回傳結果
 */
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");//設定回應編碼
        response.setContentType("application/json");//設定回應格式,前后端分離最好是json
        PrintWriter out = response.getWriter();//獲取response輸出流
        //創建回傳結果物件
        Result result = Result.error().code(401).message("請登錄!!!");
        //以json字串形式放到response輸出流中
        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();//重繪
        out.close();//關閉流
    }
}

5. Security配置類

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

import com.dd.security.entity.DdUser;
import com.dd.security.exception.RestAccessDeniedHandler;
import com.dd.security.exception.RestAuthorizationEntryPoint;
import com.dd.security.filter.JwtAuthencationTokenFilter;
import com.dd.security.service.DdUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DdUserService ddUserService;

    @Autowired
    private RestAccessDeniedHandler restAccessDeniedHandler;//當訪問介面沒有權限,自定義回傳結果
    @Autowired
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;//當未登錄或token失效時訪問介面,自定義回傳結果
    /**
     * 配置密碼決議Bean實體
     * 加載到IOC容器
     * 這是Spring Security 自定義登錄邏輯的硬性要求
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置UserDetailsService
     */
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return username->{
            DdUser user = ddUserService.getLoginInfoByUsername(username);
            if(user == null){
                return null;
            }
            return user;
        };
    }

    /**
     * 配置JWT登錄授權過濾器
     */
    @Bean
    public JwtAuthencationTokenFilter jwtAuthencationTokenFilter(){
        return new JwtAuthencationTokenFilter();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())//設定Security使用的userDetailsService,為我們上面配置的
                .passwordEncoder(passwordEncoder());//設定使用的passwordEncoder為我們上面配置的passwordEncoder
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //使用JWT不需要csrg
        http.csrf().disable()
                //使用Token,不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()//接下來配置授權
                .authorizeRequests()
                //下面授權在configure(WebSecurity web)方法中配置了,這里不需要了
//                .antMatchers("/login","/logout").permitAll()//允許訪問/login,/logout的請求無需認證即可通行
                .anyRequest().authenticated()//除了上面配置的,剩下的請求全部攔截,必須認證通過才能訪問
                .and()//接下來配置快取
                .headers()
                .cacheControl()
                ;
        //添加JWT登錄授權過濾攔截器
        http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加自定義未授權,和未登錄結果回傳,前后端分離,需要回傳狀態碼
        http.exceptionHandling()
                .accessDeniedHandler(restAccessDeniedHandler)
                .authenticationEntryPoint(restAuthorizationEntryPoint);

    }

    /**
     * 放行路徑配置
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/login",
                        "/logout",
                        "/css/**",
                        "/js/**",
                        "/swagger-ui.html",
                        "/webjars/**",
                        "/swagger-resources/**",
                        "/v2/api-docs/**"
                );
    }
}

三、實作登錄邏輯

1. 專門給登錄用戶的物體類

在這里插入圖片描述

2. controller

在這里插入圖片描述

import com.dd.common_utils.Result;
import com.dd.security.entity.DdUser;
import com.dd.security.entity.UserLogin;
import com.dd.security.service.DdUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.security.Principal;

@RestController
@Api(tags = "LoginController,登錄")
public class LoginController {

    @Autowired
    private DdUserService ddUserService;

    @ApiOperation(value = "登錄之后回傳token,如果想要帶著token請求,需要添加Bearer前綴,Bearer token,中間空格分隔")
    @PostMapping("/login")
    public Result login(@RequestBody UserLogin userLogin, HttpServletRequest request){
        if(userLogin == null){
            Result.error().message("請輸入用戶名和密碼");
        }
        if(userLogin.getUsername()==null){
            Result.error().message("請輸入用戶名");
        }
        if(userLogin.getPassword()==null){
            Result.error().message("請輸入密碼");
        }
        return ddUserService.login(userLogin,request);
    }

    @ApiOperation(value = "獲取當前登錄用戶資訊")
    @GetMapping("/login/info")
    public Result loginInfo(@ApiParam(value ="全域物件,security將資訊設定到全域,通過這個就可以獲取")
                            Principal principal){
        if(principal == null){
            return Result.error().message("用戶資訊不存在");
        }
        String username = principal.getName();//從全域中獲取username
        //根據用戶名獲取用戶物件
        DdUser ddUser =  ddUserService.getLoginInfoByUsername(username);
        if(ddUser == null){
            return Result.error().message("用戶資訊不存在");
        }
        ddUser.setPassword(null);//無論如何都不可以回傳密碼
        return Result.ok().message("用戶資訊獲取成功").data("loginInfo",ddUser);
    }

    @ApiOperation(value = "退出登錄")
    @PostMapping("/logout")
    public Result logout(){
        //這里省略了退出登錄邏輯,比如銷毀token,銷毀資料庫,redis中快取等,因為沒有引入redis,所以直接回傳退出成功
        return Result.ok().message("退出登錄");
    }
}

3. service

在這里插入圖片描述
在這里插入圖片描述

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dd.common_utils.Result;
import com.dd.security.entity.DdUser;

import com.dd.security.entity.UserLogin;
import com.dd.security.mapper.DdUserMapper;
import com.dd.security.service.DdUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dd.security.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
@Service
public class DdUserServiceImpl extends ServiceImpl<DdUserMapper, DdUser> implements DdUserService {

    @Autowired
    private DdUserMapper ddUserMapper;

    @Autowired
    private UserDetailsService userDetailsService;//spring security UserDetailsService,需要我們自己實作它

    @Autowired
    private PasswordEncoder passwordEncoder;//PasswordEncoder,需要我們自己配置出來

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("${jwt.tokenHead}")
    private String tokenHead;
    /**
     * 登錄之后回傳token
     */
    @Override
    public Result login(UserLogin userLogin, HttpServletRequest request) {
        //獲取userDetails物件,這里我們userDetailsServiceImpl實作的是,它通過用戶名查詢了資料庫的密碼
        UserDetails userDetails = userDetailsService.loadUserByUsername(userLogin.getUsername());
        //如果用戶為空,
        if(userDetails==null){
            return Result.error().message("用戶不存在");
        }
        //密碼匹配失敗
        if(!passwordEncoder.matches(userLogin.getPassword(),userDetails.getPassword())){
            return Result.error().message("密碼錯誤");
        }
        //下面這個因為我們DdUser將isEnabled寫死為true,所以就不寫這些判斷了,其實都應該寫成屬性,存盤到資料庫
//        if(!userDetails.isEnabled()){
//            return Result.error().message("用戶被禁言,請聯系管理員");
//        }

        //更新登錄用戶物件UserDetails 如果獲取物件成功,沒有錯誤,那么使用Spring Security我們需要將物件設定到UserDetails中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);//將其放在Security全域中
        //生成token,回傳
        String jwtToken = jwtTokenUtil.getJwtToken(userDetails);
        return Result.ok().message("登錄成功,獲取token")
                .data("token",jwtToken)//token字串
                .data("tokenHead",tokenHead);//tokenheader,key
    }

    /**
     * 根據用戶名獲取登錄物件
     * @param username
     * @return
     */
    @Override
    public DdUser getLoginInfoByUsername(String username) {
        DdUser ddUser = ddUserMapper.selectOne(new QueryWrapper<DdUser>().eq("username", username));
        if(ddUser == null){
            return null;
        }
        return ddUser;
    }
}

四、通過swagger測驗

1. 配置swagger,攜帶指定請求頭authorization,規定哪些路徑需要認證(只有規定的路徑才會攜帶請求頭)

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

package com.dd.service_base.config;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableSwagger2//表中此類為Swagger2
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig() {
        List<Parameter> pars = new ArrayList<Parameter>();
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")//分組名
                .apiInfo(webApiInfo())//在線檔案的資訊,傳入ApiInfo物件,就是下面內個方法回傳的物件
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))//路徑中包含admin時不顯示資訊
                .paths(Predicates.not(PathSelectors.regex("/error/.*")))
                .build()
                //因為我們開啟了securityJWT驗證,所以我們也需要給Swagger配置頭資訊
                .securityContexts(securityContexts())
                .securitySchemes(securitySchemes())
                ;
    }

    private ApiInfo webApiInfo() {
        return new ApiInfoBuilder()
                .title("gulischool 介面 API 檔案")
                .description("展示先做基礎功能,后面再添加業務")
                .termsOfServiceUrl("https://www.dd.com/aa/")
                .version("1.0")
                .contact(new Contact("Helen","http://dd.com","915501928@qq.com"))
                .build();
    }


    private List<ApiKey> securitySchemes(){
        //設定頭資訊
        ArrayList<ApiKey> result = new ArrayList<>();
        ApiKey apiKey = new ApiKey("Authorization", "Authorization", "Header");
        result.add(apiKey);
        return result;
    }

    /**
     * 設定哪些路徑需要授權
     * @return
     */
    private List<SecurityContext> securityContexts(){
        //設定需要授權認證的路徑
        ArrayList<SecurityContext> result = new ArrayList<>();
        result.add(getContextByPath("/service_animation/.*"));
//        result.add(getContextByPath("/login/.*"));
        result.add(getContextByPath("/login/info"));
        result.add(getContextByPath("/logout"));
        result.add(getContextByPath("/security/*"));
        return result;
    }

    private SecurityContext getContextByPath(String pathRegex) {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
//                .forPaths(PathSelectors.regex(pathRegex))
                .forPaths(PathSelectors.regex("^(?!auth).*$"))
                .build();
    }

    private List<SecurityReference> defaultAuth() {
        ArrayList<SecurityReference> result = new ArrayList<>();
        //設定授權范圍為全域global
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");

        //官方要求必須放在陣列中,所以將其放在陣列中
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0]=authorizationScope;
        //官方要求List中添加new SecurityReference("Authorization",authorizationScopes)
        result.add(new SecurityReference("Authorization",authorizationScopes));
        return result;
    }
}

2. 測驗

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

前端呢,只需要登錄后按規則保存token,請求時,攜帶這個token就可以訪問了

五、驗證碼實作

更改物體類,我們登錄時需要傳入驗證碼了

在這里插入圖片描述

引入依賴

在這里插入圖片描述

1. 生成驗證碼

驗證碼配置類

在這里插入圖片描述

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
 * 驗證碼配置類
 */
@Configuration
public class CaptchaConfig {
    @Bean
    public DefaultKaptcha defaultKaptcha(){
        //驗證碼生成器
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        //配置
        Properties properties = new Properties();
        //時候有邊框
        properties.setProperty("kaptcha.border","yes");
        //設定邊框顏色
        properties.setProperty("kaptcha.border.color","105,179,98");
        //驗證碼
        properties.setProperty("kaptcha.session.key","code");
        //驗證碼文本字符顏色,默認黑
        properties.setProperty("kaptcha.textproducer.font.color","blue");
        //設定字體樣式
        properties.setProperty("kaptcha.textproducer.font.names","宋體,楷體,微軟雅黑");
        //設定字體大小默認40
        properties.setProperty("kaptcha.textproducer.font.size","30");
        //驗證碼文本字符內容范圍 默認abced2345678gfynmnpwx
//        properties.setProperty("kaptcha.textproducer.char.string","");
        //字符長度,默認5
        properties.setProperty("kaptcha.textproducer.char.length","4");
        //字符間距,默認2
        properties.setProperty("kaptcha,textproducer.char.space","4");
        //驗證碼圖片寬度,默認208
        properties.setProperty("kaptcha.image.width","100");
        //驗證碼圖片高度,默認40
        properties.setProperty("kaptcha.image.height","40");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}
controller

在這里插入圖片描述

import com.google.code.kaptcha.impl.DefaultKaptcha;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;

@RestController
public class CaptchaController {
    @Autowired
    private DefaultKaptcha defaultKaptcha;

    @ApiOperation(value = "驗證碼,以image/jpeg格式固定回應")
    @GetMapping(value = "/captcha",produces = "image/jpeg")
    public void captcha(HttpServletRequest request, HttpServletResponse response)  {

        /**定義response輸出型別為image/jpeg型別**/
        response.setDateHeader("Expires",0);
        // Set standard HTTP/1.1 no-cache headers.
        response.setHeader("Cache-Control","no-store, no-cache, must-revalidate");
        // Set IE extended HTTP/1.1 no-cache headers(use addHeader)
        response.addHeader("Cache-Control","post-check=0,pre-check=0");
        // Set standard HTTP/1.0 no-cache header.
        response.setHeader("Pragma","no-cache");
        // return a jpeg
        response.setContentType("image/jpeg");
        /**生成驗證碼 begin**/
        String text = defaultKaptcha.createText();
        System.out.println("驗證碼:"+text);
        //將驗證碼文本內容放入session
        request.getSession().setAttribute("captcha",text);
        //根據文本驗證碼內容創建圖形驗證碼
        BufferedImage image = defaultKaptcha.createImage(text);
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            //輸出流輸出.jpg格式圖片
            ImageIO.write(image,"jpg",outputStream);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
swagger測驗

在這里插入圖片描述
在這里插入圖片描述

2. 修改service邏輯

在這里插入圖片描述

        //從session中獲取驗證碼
        String captcha = (String) request.getSession().getAttribute("captcha");
        //如果驗證碼為空,或者驗證碼和session中驗證碼不一致(忽略大小寫比較),回傳錯誤
        if(StringUtils.isEmpty(captcha)||!captcha.equalsIgnoreCase(userLogin.getCode())){
            return Result.error().message("驗證碼錯誤!!!");
        }

六、實作權限管理系統

因為篇幅限制,我將其放在這篇文章中https://blog.csdn.net/grd_java/article/details/121932440

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/382308.html

標籤:其他

上一篇:UNITY3D 動作游戲開發教程《怪物獵人》

下一篇:PLCnext to Proficloud Technology User manual

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more