Springboot+MybatisPlus+Spring-security-Jwt Api接口實現權限認證實戰

一.前言吐槽

剛開始學習,網上找了很多例子,可能我比較小白,踩了很多坑,不知道爲什麼有些大佬,都不屑於測試下的,直接丟倉庫了,下載很多都是跑不起來的。看得我一臉懵逼的。好啦不吐槽了,記錄下自己學習的東東,開始自己的表演!

二.代碼

**2.1源碼地址:**https://github.com/myjsonman/spring-security-kavy.git

2.2代碼結構目錄:
在這裏插入圖片描述

2.3 在IDEA 創建一個簡單的Maven 工程添加pom對應的依賴

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <!--  加密的  -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
	<!--  JWT  -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mybatisplus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>net.minidev</groupId>
            <artifactId>json-smart</artifactId>
            <version>2.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.noggit</groupId>
            <artifactId>noggit</artifactId>
            <version>0.6</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

核心依賴:

security:

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>

JWt:

  <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

2.4 簡單的表設計:
設計有點粗超,見諒。

權限表:
CREATE TABLE `user_roles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  `role_str` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;

用戶表:
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_img` varchar(255) DEFAULT NULL,
  `updatetime` timestamp NULL DEFAULT NULL,
  `status` char(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;

角色表:
CREATE TABLE `role` (
  `id` int(50) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `updatetime` timestamp NULL DEFAULT NULL,
  `role_desc` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

在這裏寫下項目的流程:

1.創建一個springboot項目
2.導入springSecurity和jwt 依賴
3.創建實體類
4.創建service層
5.創建mapper層
6.創建UserDetail去實現UserDetails
7.創建JwtUserDetailsServiceImpl去實現UserDetailsService
8.創建用戶登錄獲取權限的攔截器
9.配置config 處理攔截器
10.jwt的工具類生成token

開始Spring boot的一個簡單API

2.5 實體

@Data
public class Role {

    private Integer id;

    private String roleName;

    private Timestamp updatetime;

    private String roleDesc;

}
@Data
public class User implements Serializable {

    private Integer id;
    
    @NotBlank(message = "用戶名不能爲空")
    @Size(min=5, max=20,message = "用戶名不能小於5位,大於20位")
    private String username;

    @NotBlank(message = "密碼不能爲空")
    @Size(min=6,max = 20,message = "密碼不能小於6位,大於20位")
    private String password;

    private String userImg;

    private Date updatetime;

    private String status;
}
@Data
public class UserRoles {
    private  Integer id;
    private  Integer userId;
    private  Integer roleId;
    private String roleStr;

}

2.6 service:


public interface UserService {

    /**
     * 註冊用戶
     * @return
     */
    void register(User user,String str);


    /**
     * 登陸
     * @param username
     * @param password
     * @return
     */
    ResponseUserToken login(String username, String password);

}

2.7 實現類

package com.kavy.springsecuritydemo.service.serviceImp;

import com.kavy.springsecuritydemo.entity.User;
import com.kavy.springsecuritydemo.entity.UserDetail;
import com.kavy.springsecuritydemo.entity.UserRoles;
import com.kavy.springsecuritydemo.exception.CustomException;
import com.kavy.springsecuritydemo.mapper.UserMapper;
import com.kavy.springsecuritydemo.mapper.UserRolesMapper;
import com.kavy.springsecuritydemo.result.ResultCode;
import com.kavy.springsecuritydemo.result.ResultJson;
import com.kavy.springsecuritydemo.service.UserService;
import com.kavy.springsecuritydemo.utils.JwtUtils;
import com.kavy.springsecuritydemo.utils.ResponseUserToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.apache.commons.lang.StringUtils;


import java.util.Date;
@Service
public class UserServiceImp implements UserService {

    @Autowired
    UserMapper userMapper;
    @Autowired
    private UserRolesMapper userRolesMapper;
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private AuthenticationManager authenticationManager;


    @Override
    public void register(User user,String str) {

        //查詢用戶
        User oldUser = userMapper.findByUsername(user.getUsername());

        if (oldUser != null) {

           throw new CustomException(ResultJson.failure(ResultCode.BAD_REQUEST, "用戶已存在"));
        }
        //加密
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        user.setPassword(encoder.encode(user.getPassword()));
        user.setUpdatetime(new Date(System.currentTimeMillis()));
        user.setStatus("0");
        userMapper.insert(user);

        if (StringUtils.isNotBlank(str)){
            //權限插入
            String[] roles = str.split(",");
            for (String role : roles) {
                //如果原先有綁定權限就刪除
               // userRolesMapper.deleteById(user.getId());

                UserRoles userRoles = new UserRoles();
                userRoles.setUserId(user.getId());
                userRoles.setRoleId(Integer.parseInt(role));
                userRoles.setRoleStr(str);
                userRolesMapper.insert(userRoles);
            }
        }

    }

    @Override
    public ResponseUserToken login(String username, String password) {
        //用戶驗證
        final Authentication authentication = authenticate(username, password);
        //存儲認證信息
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //生成token ,查看源代碼會發現調用getPrincipal()方法會返回一個實現了`UserDetails`接口的對象
        final UserDetail userDetail = (UserDetail) authentication.getPrincipal();

        //通過工具類生成token
         final String token = "Bearer "+jwtUtils.generateAccessToken(userDetail);

        //存儲token
        jwtUtils.putToken(username, token);
        // 學習 測試用,把用戶的信息也返回了
        return new ResponseUserToken(token, userDetail);
    }


    private Authentication authenticate(String username, String password) {
        try {
            //該方法會去調用userDetailsService.loadUserByUsername()去驗證用戶名和密碼,如果正確,則存儲該用戶名密碼到“security 的 context中”
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (DisabledException | BadCredentialsException e) {
            throw new CustomException(ResultJson.failure(ResultCode.LOGIN_ERROR, e.getMessage()));
        }
    }
}

2.8 Mapper
注:說是MybatisPlus 其實就是保存數據的時候用了,不過我已經整合好了,大家Mybatis 和Plus 兩個可以無縫切換使用 。 BaseMapper<> 這個是MybatisPlus 的寫法;

RoleMapper:

package com.kavy.springsecuritydemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kavy.springsecuritydemo.entity.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;


public interface RoleMapper extends BaseMapper<Role> {

    /**
     * 創建用戶角色
     * @param userId
     * @param roleId
     * @return
     */
    int insertRole(long userId, long roleId);

    /**
     * 根據角色id查找角色
     * @param roleId
     * @return
     */
    Role findRoleById(long roleId);

    /**
     * 根據用戶id查找該用戶角色
     * @param userId
     * @return
     */
    List<Role> findRoleByUserId(@Param("userId") Integer userId);
}

UserMapper:

@Mapper
public interface UserMapper extends BaseMapper<User> {

    User findByUsername(String username);


}

UserRolesMapper:

public interface UserRolesMapper extends BaseMapper<UserRoles> {
}

roleMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kavy.springsecuritydemo.mapper.RoleMapper">



    <insert id="insertRole">
        insert into user_roles (user_id, role_id) VALUES (#{userId}, #{roleId});
    </insert>


    <select id="findRoleById" resultType="Role">
      select id, role_name, role_desc from role where id = #{roleId}
    </select>

    <select id="findByUsername" parameterType="String" resultType="User">
    SELECT id, username, password from user where username = #{username};
    </select>
    <select id="findRoleByUserId" resultType="Role">
        select * from role where id in (SELECT role_id from user_roles where user_id = #{userId});
    </select>


</mapper>

userMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kavy.springsecuritydemo.mapper.UserMapper">


    <insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        insert into user (username, password,updatetime,status) VALUES (#{username}, #{password},#{updatetime},#{status});
    </insert>
    <select id="findByUsername" parameterType="String" resultType="User">
    SELECT id, username, password from user where username = #{username};
    </select>

    <select id="queryByUsername" parameterType="String" resultType="User">
    SELECT id, username, password from user where username = #{username};
    </select>


</mapper>

Controller:

package com.kavy.springsecuritydemo.controller;

import com.kavy.springsecuritydemo.entity.User;
import com.kavy.springsecuritydemo.result.ResultCode;
import com.kavy.springsecuritydemo.result.ResultJson;
import com.kavy.springsecuritydemo.service.UserService;
import com.kavy.springsecuritydemo.utils.ResponseUserToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping("/api")
public class UserController {

    @Value("${jwt.header}")
    private String tokenHeader;

    @Autowired
    UserService userService;

    @GetMapping("/hello")
    public String hello(){

        return "hi security!";
    }

    /**
     * 註冊
     * @param user
     */
    @PostMapping("/register")
    public ResultJson signUp( User user ,String str) {
        if (user==null){
            ResultJson.failure(ResultCode.BAD_REQUEST);
        }
        userService.register(user,str);

         return ResultJson.success();

    }

    /**
     * 獲取token
     * @param user
     * @return
     */
    @PostMapping("/login")
    public ResultJson<ResponseUserToken> login(@RequestBody User user) {

        final ResponseUserToken response = userService.login(user.getUsername(), user.getPassword());
        return ResultJson.ok(response);
    }



}

標題黨

臥槽 發現貼得有點囉嗦,Security 和JWT呢。別急大佬,別噴,馬上=。=上代碼

網咯配圖(咱也畫不出來,也不敢問,都是圍觀大佬的):

在這裏插入圖片描述
1.開始整合JWT認證

創建一個JwtUserDetailsServiceImpl 去實現 UserDetailsService接口,UserDetailsService接口就一個方法 loadUserByUsername()

package com.kavy.springsecuritydemo.service.serviceImp;

import com.kavy.springsecuritydemo.entity.Role;
import com.kavy.springsecuritydemo.entity.User;
import com.kavy.springsecuritydemo.entity.UserDetail;
import com.kavy.springsecuritydemo.mapper.RoleMapper;
import com.kavy.springsecuritydemo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.password.PasswordEncoder;

import java.util.List;

@Configuration
public class JwtUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
   private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

            User user = userMapper.findByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException(String.format("No userDetail found with username '%s'.", username));
            }
            //查詢權限封裝
        List<Role> roleByUserId = roleMapper.findRoleByUserId(user.getId());

        return new UserDetail(user.getUsername(),roleByUserId,user.getPassword());

}


}

再創建UserDetail 去實現UserDetails 接口,JWT通過這個接口去獲取用戶密碼和權限

package com.kavy.springsecuritydemo.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@Data
public class UserDetail implements UserDetails {

    private long id;
    private String username;
    private String password;
    private List<Role> roles;
    //private Collection<? extends GrantedAuthority> authorities;

    public UserDetail(
            String username,
            List<Role> roles,
            String password) {
        this.username = username;
        this.password = password;
        this.roles = roles;
    }
    //getAuthorities獲取用戶包含的權限,返回權限集合,權限是一個繼承了GrantedAuthority的對象;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 根據自定義邏輯來返回用戶權限,如果用戶權限返回空或者和攔截路徑對應權限不同,驗證不通過
        if (!roles.isEmpty()){

            List<GrantedAuthority> authorities = new ArrayList<>();
            for (Role role : roles) {
                authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
            }
            return authorities;
        }
        return null;
    }

    //getPassword和getUsername用於獲取密碼和用戶名;
    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    //isAccountNonExpired方法返回boolean類型,用於判斷賬戶是否未過期,未過期返回true反之返回false;
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    //isAccountNonLocked方法用於判斷賬戶是否未鎖定;
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    //isCredentialsNonExpired用於判斷用戶憑證是否沒過期,即密碼是否未過期;
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    //isEnabled方法用於判斷用戶是否可用。
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }
}

攔截器filter

package com.kavy.springsecuritydemo.filter;

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

import com.kavy.springsecuritydemo.service.serviceImp.JwtUserDetailsServiceImpl;
import com.kavy.springsecuritydemo.utils.JwtUtils;
import org.apache.commons.lang.StringUtils;
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.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import io.jsonwebtoken.ExpiredJwtException;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {
	@Autowired
	private JwtUserDetailsServiceImpl jwtUserDetailsService;
	
	@Autowired
	private JwtUtils jwtTokenUtil;
	

	
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws ServletException, IOException {
		//獲取header 的Authorization
		final String requestTokenHeader = request.getHeader("Authorization");
		String username = null;
		String jwtToken = null;
		// JWT報文表頭的格式是"Bearer token". 去除"Bearer ",直接獲取token
		// only the Token
		if (StringUtils.isNotEmpty(requestTokenHeader) && requestTokenHeader.startsWith("Bearer ")) {
			jwtToken = requestTokenHeader.substring(7);
			try {
				//獲取username
				username = jwtTokenUtil.getUsernameFromToken(jwtToken);
			} catch (IllegalArgumentException e) {
				System.out.println("Unable to get JWT Token");
			} catch (ExpiredJwtException e) {
				System.out.println("JWT Token has expired");
			}
		} else {
			logger.warn("JWT Token does not begin with Bearer String");
		}
		// Once we get the token validate it.
		if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
			//獲取 userDetails
			UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
			// if token is valid configure Spring Security to manually set
			// authentication
			if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
				UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
						userDetails, null, userDetails.getAuthorities());
				usernamePasswordAuthenticationToken
				.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

				SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
			}
		}
		chain.doFilter(request, response);
	}
}

錯誤返回:

package com.kavy.springsecuritydemo.filter;

import java.io.IOException;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

/**
 * 用來解決匿名用戶訪問無權限資源時的異常
 */
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
	private static final long serialVersionUID = 1L;

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException {
		response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
	}
}

核心配置config:

package com.kavy.springsecuritydemo.config;

import com.kavy.springsecuritydemo.filter.JwtAuthenticationEntryPoint;
import com.kavy.springsecuritydemo.filter.JwtRequestFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @EnableWebSecurity註解繼承WebSecurityConfigurerAdapter的類,這樣就構成了Spring Security的配置。
 *
 */

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    public WebSecurityConfig(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }


        @Autowired
        private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // We don't need CSRF for this example
        httpSecurity.csrf().disable()
                //用來解決匿名用戶訪問無權限資源時的異常
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
                //禁用session 無狀態
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // dont authenticate this particular request
                .and()
                .authorizeRequests()
                //註冊 register 和登錄 login 不需要驗證 就可以訪問,其他的都需要token驗證纔可以訪問
                .antMatchers(HttpMethod.POST, "/api/login", "/api/register", "/error/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

        // disable page caching
        httpSecurity
                .headers()
                .frameOptions().sameOrigin()  // required to set for H2 else H2 Console will be blank.
                .cacheControl();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);

    }

/*    @Override
    protected void configure(HttpSecurity http) throws Exception {
         http.formLogin() // 表單方式
      //  http.httpBasic() // HTTP Basic方式
                .loginPage("/login.html")   //.loginPage("/login.html")指定了跳轉到登錄頁面的請求URL
                .loginProcessingUrl("/login") //.loginProcessingUrl("/login")對應登錄頁面form表單的action="/login"
                .and()
                .authorizeRequests() // 授權配置
                .antMatchers("/login.html").permitAll()   // 表示跳轉到登錄頁面的請求不被攔截,否則會進入無限循環。
                .anyRequest()  // 所有請求
                .authenticated() // 都需要認證
                .and().csrf().disable();
    }*/



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


}

JWT的工具類:
用來生成token的

package com.kavy.springsecuritydemo.utils;

import com.kavy.springsecuritydemo.entity.UserDetail;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


@Component
public class JwtUtils {

    private static final String CLAIM_KEY_USER_ID = "user_id";
    private static final String CLAIM_KEY_AUTHORITIES = "scope";

    private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);

    //密匙
    @Value("${jwt.secret}")
    private String secret;
    //有效期
    @Value("${jwt.expiration}")
    private Long access_token_expiration;

    //刷新密匙
    @Value("${jwt.expiration}")
    private Long refresh_token_expiration;

    private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;


    //獲取token
    public String generateAccessToken(UserDetail userDetail) {
        Map<String, Object> claims = generateClaims(userDetail);
        claims.put(CLAIM_KEY_AUTHORITIES, authoritiesToArray(userDetail.getAuthorities()).get(0));
        return generateAccessToken(userDetail.getUsername(), claims);
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }


    public void putToken(String userName, String token) {
        tokenMap.put(userName, token);
    }


    /**
     * 生成token的過期時間
     * @return
     */
    private Date generateExpirationDate(long expiration) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 從token中獲取claims
     *
     **/
    public Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     *  獲取用戶名
     * @param token
     * @return
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }
    /**
     *  獲取token 過期時間
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /**
     *     判斷token 是否過期
     * @param token
     * @return
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     *  判斷token是否可以刷新
     * @param token
     * @return
     */
    public Boolean canTokenBeRefreshed(String token) {
        final Date created = getCreatedDateFromToken(token);
        return !isTokenExpired(token);
    }

    /**
     * 從token中獲取創建時間
     * @param token
     * @return
     */
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = claims.getIssuedAt();
        } catch (Exception e) {
            created = null;
        }
        return created;
    }
    /**
     *   刷新token
     * @param token
     * @return
     */

    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            refreshedToken = generateAccessToken(claims.getSubject(), claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }


    /**
     *  claims
     * @param userDetail
     * @return
     */
    private Map<String, Object> generateClaims(UserDetail userDetail) {
        Map<String, Object> claims = new HashMap<>(16);
        claims.put(CLAIM_KEY_USER_ID, userDetail.getId());
        System.out.println("userDetail.getId()--------->>>"+userDetail.getId());
        System.out.println("userDetail--->"+userDetail.toString());
        return claims;
    }

    // 傳入 username 過期時間 claims 獲取token
    private String generateAccessToken(String subject, Map<String, Object> claims) {
        // subject  --->userDetail.getUsername()  access_token_expiration過期時間
        return generateToken(subject, claims, access_token_expiration);
    }


    private List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
        List<String> list = new ArrayList<>();
        for (GrantedAuthority ga : authorities) {
            list.add(ga.getAuthority());
        }
        return list;
    }



    /**
     *      生成token
     * @param subject
     * @param claims
     * @param expiration
     * @return
     */
    private String generateToken(String subject, Map<String, Object> claims, long expiration) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject) //username  設置面向用戶
               // .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date())  //簽發時間
                .setExpiration(generateExpirationDate(expiration)) //生成token的過期時間
                .compressWith(CompressionCodecs.DEFLATE)
                .signWith(SIGNATURE_ALGORITHM, secret)  //SignatureAlgorithm.HS512, secret 生成簽名
                .compact();
    }

}

整個Security就完成了,在此我只做了註冊和獲取token的兩個,嗯,還有一些返回的封裝和錯誤碼返回處理,沒有貼出來,具體可以去看下源碼,其他的可以根據自己需求寫了;

測試:
沒有獲取到token,所以會報錯
在這裏插入圖片描述
註冊:
在這裏插入圖片描述

註冊成功,通過註冊的用戶和密碼獲取token
在這裏插入圖片描述

通過獲取到的token,再次訪問hello接口
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章