Spring Boot 鑒權之—— springboot2.0.4+mybatis 整合的完整用例


自上一篇文章的基礎上,Spring Boot 鑒權之—— JWT 鑒權我做了一波springboot2.0.4+mybatis 的整合。

參考文章: Spring Boot+Spring Security+JWT 實現 RESTful Api 權限控制   

  源碼地址:

               碼雲:https://gitee.com/region/spring-security-oauth-example/tree/master/spring-security-jwt

springboot2.0.4+mybatis pom.xml:

 這里由於springboot2.0.4沒有默認的passwordencoder,也就是說我們登錄不能明文登錄,所以為了方便期間,我直接使用了數據庫。

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        
         <!-- Spring-Mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version>
            </dependency>
 
        <!-- MySQL 連接驅動依賴 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

config配置修改:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.jwt.server.filter.JwtAuthenticationFilter;
import com.jwt.server.filter.JwtLoginFilter;
import com.jwt.server.provider.CustomAuthenticationProvider;

/**
 * 通過SpringSecurity的配置,將JWTLoginFilter,JWTAuthenticationFilter組合在一起
 * 
 * @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) 在springboot1.5.8的時候該注解是可以用的
 *                                                  具體看源碼
 * @author zyl
 *
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Qualifier("userDetailServiceImpl")
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

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

        // 自定義 默認
        http.cors().and().csrf().disable().authorizeRequests().antMatchers("/users/signup").permitAll().anyRequest()
                .authenticated().and().addFilter(new JwtLoginFilter(authenticationManager()))// 默認登錄過濾器
                .addFilter(new JwtAuthenticationFilter(authenticationManager()));// 自定義過濾器

    }
    
    // 該方法是登錄的時候會進入
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
        // 使用自定義身份驗證組件   手動注入加密類
        auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService, bCryptPasswordEncoder));
    }

}

自定義身份驗證組件

package com.jwt.server.provider;

import java.util.ArrayList;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
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;


/**
 * 自定義身份認證驗證組件
 * @author zyl
 *
 */
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private UserDetailsService userDetailsService;

    private BCryptPasswordEncoder bCryptPasswordEncoder;

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

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 獲取認證的用戶名 & 密碼
        String name = authentication.getName();
        String password = authentication.getCredentials().toString();
        // 認證邏輯
        UserDetails userDetails = userDetailsService.loadUserByUsername(name);
        if (null != userDetails) {
            if (bCryptPasswordEncoder.matches(password, userDetails.getPassword())) {
                // 這里設置權限和角色
                ArrayList<GrantedAuthority> authorities = new ArrayList<>();
                authorities.add( new GrantedAuthorityImpl("ROLE_ADMIN"));
                authorities.add( new GrantedAuthorityImpl("ROLE_API"));
                authorities.add( new GrantedAuthorityImpl("AUTH_WRITE"));
                // 生成令牌 這里令牌里面存入了:name,password,authorities, 當然你也可以放其他內容
                Authentication auth = new UsernamePasswordAuthenticationToken(name, password, authorities);
                return auth;
            } else {
                throw new BadCredentialsException("密碼錯誤");
            }
        } else {
            throw new UsernameNotFoundException("用戶不存在");
        }
    }

    /**
     * 是否可以提供輸入類型的認證服務
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

}

權限類型,負責存儲權限和角色

package com.jwt.server.provider;

import org.springframework.security.core.GrantedAuthority;

/**
 * 權限類型,負責存儲權限和角色
 *
 * @author zyl
 */
public class GrantedAuthorityImpl implements GrantedAuthority {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    
    private String authority;

    public GrantedAuthorityImpl(String authority) {
        this.authority = authority;
    }

    public void setAuthority(String authority) {
        this.authority = authority;
    }

    @Override
    public String getAuthority() {
        return this.authority;
    }
}

 定義數據庫service、dao文件

package com.jwt.server.service;

import com.jwt.server.domain.UserInfo;

/**
 * 用戶service
 * @author zyl
 *
 */
public interface UserService {

    /**
     * 根據用戶名查詢用戶是否存在
     * @param username
     * @return
     */
    public UserInfo findByUsername(String username);

    /**
     * 添加用戶
     * @param user
     * @return
     */
    public UserInfo save(UserInfo user);

}
package com.jwt.server.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.jwt.server.domain.UserInfo;
import com.jwt.server.mapper.UserMapper;
import com.jwt.server.service.UserService;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper  usermapper;
    
    @Override
    public UserInfo findByUsername(String username) {
        return usermapper.findByUsername(username);
    }

    @Override
    public UserInfo save(UserInfo user) {
        return usermapper.save(user);
    }

}

修改之前定義的UserDetailServiceImpl文件為:

package com.jwt.server.service.impl;

import static java.util.Collections.emptyList;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.jwt.server.domain.UserInfo;
import com.jwt.server.service.UserService;

/**
 * 
 * @author zyl
 *
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    protected UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo user = userService.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                emptyList());
    }
}

增加IdGenerator id生成類

 

package com.jwt.server.util;


import java.net.InetAddress;
import java.net.UnknownHostException;

import org.apache.commons.lang3.time.DateFormatUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * 與snowflake算法區別,返回字符串id,占用更多字節,但直觀從id中看出生成時間
 *
 */
@Slf4j
public enum IdGenerator {
    /**
     * 每個要生成的序號類型對應一個序號
     */
    USER_TRANSID("1");

    private long workerId;   //用ip地址最后幾個字節標示
    private long datacenterId = 0L; //可配置在properties中,啟動時加載,此處默認先寫成0
    private long sequence = 0L;
    private final long twepoch = 1516175710371L;
    private final long workerIdBits = 1L;
    private final long datacenterIdBits = 2L;
    private final long sequenceBits = 3L;
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private long sequenceMask = -1L ^ (-1L << sequenceBits); //4095
    private long lastTimestamp = -1L;

    private String index;
    
    IdGenerator(String ind) {
        this.index = ind;
        workerId = 0x000000FF & getLastIP();
    }

    public synchronized String nextId() {
        long timestamp = timeGen(); //獲取當前毫秒數
        //如果服務器時間有問題(時鍾后退) 報錯。
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        //如果上次生成時間和當前時間相同,在同一毫秒內
        if (lastTimestamp == timestamp) {
            //sequence自增,因為sequence只有12bit,所以和sequenceMask相與一下,去掉高位
            sequence = (sequence + 1) & sequenceMask;
            //判斷是否溢出,也就是每毫秒內超過4095,當為4096時,與sequenceMask相與,sequence就等於0
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp); //自旋等待到下一毫秒
            }
        } else {
            sequence = 0L; //如果和上次生成時間不同,重置sequence,就是下一毫秒開始,sequence計數重新從0開始累加
        }
        lastTimestamp = timestamp;

        long suffix =  ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
        
        String datePrefix = DateFormatUtils.format(timeGen(), "yyyyMMddHHmmss");
        return datePrefix +index + suffix;
    }
    

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    private byte getLastIP(){
        byte lastip = 0;
        try{
            InetAddress ip = InetAddress.getLocalHost();
            byte[] ipByte = ip.getAddress();
            lastip = ipByte[ipByte.length - 1];
        } catch (UnknownHostException e) {
            log.error("UnknownHostException error:{}", e.getMessage());
        }
        return lastip;
    }
    
    public static void main(String[] args) {
        IdGenerator id = IdGenerator.USER_TRANSID;
        for (int i = 0; i < 1000; i++) {
            String serialNo = id.nextId();
            System.out.println(serialNo + "===" + serialNo.length());
        }
    }
}

mapper

package com.jwt.server.mapper;



import com.jwt.server.domain.UserInfo;



public interface UserMapper {



    /**

     * 根據用戶名查詢用戶是否存在

     * 

     * @param username

     * @return

     */

    public UserInfo findByUsername(String username);


    /**

     * 添加用戶

     * 

     * @param user

     * @return

     */

    public UserInfo save(UserInfo user);

}

mapper.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.jwt.server.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.jwt.server.domain.UserInfo">
    <id column="id" jdbcType="VARCHAR" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="password" jdbcType="VARCHAR" property="password" />
  </resultMap>

  <!--用戶登錄查詢  -->
  <select id="findByUsername" parameterType="java.lang.String" resultMap="BaseResultMap">
     select id,username,password from tb_user where username=#{username,jdbcType=VARCHAR}
  </select>
  
  <insert id="save" parameterType="com.jwt.server.domain.UserInfo">
        INSERT INTO tb_user
        (id,username,password) VALUES
        (#{id,jdbcType=VARCHAR},#{username,jdbcType=VARCHAR},#{password,jdbcType=VARCHAR})
  </insert>
  
</mapper>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer" />
        <typeAlias alias="Long" type="java.lang.Long" />
        <typeAlias alias="HashMap" type="java.util.HashMap" />
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
        <typeAlias alias="ArrayList" type="java.util.ArrayList" />
        <typeAlias alias="LinkedList" type="java.util.LinkedList" />
    </typeAliases>
</configuration>

application.yml配置

#公共配置與profiles選擇無關 mapperLocations指的路徑是src/main/resources
mybatis:
  typeAliasesPackage: com.jwt.server.domain
  mapperLocations: classpath:mapper/*.xml


---

#開發配置

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?prepStmtCacheSize=517&cachePrepStmts=true&autoReconnect=true&characterEncoding=utf-8&allowMultiQueries=true
    username: root
    password: tiger
    

修改啟動類掃描包

package com.jwt.server;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootApplication
@MapperScan("com.jwt.server.mapper")//
public class SpringJwtApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringJwtApplication.class, args);
    }
    
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

測試:

自定義登錄測試:

好了ok啦。

需要注意的是:

在springboot2.0.4版本的時候由於沒有默認的passwordencoder,因此需要手動注入。如果不注入會在鑒權的時候報如下錯誤

 

如果測試會會有如下情況,說明你注入后未給密碼加密

並且這里如果沒有存儲我們登錄的信息時,可能也會有個坑,就是密碼加密后與原密碼做對比會報如下錯誤

一般情況下我們用加密后,在授權的時候回去對比密碼

這個錯誤就是會在這個地方產生的。解決辦法

自定義身份驗證類

自行調用,確保密碼一致就ok。具體請看源碼分析。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM