Spring Boot 鉴权之—— springboot2.0.4+mybatis 整合的完整用例

Posted 徐徐图之

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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。具体请看源码分析。

 

以上是关于Spring Boot 鉴权之—— springboot2.0.4+mybatis 整合的完整用例的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot项目鉴权的4种方法

Spring Boot中使用Sa-Token实现轻量级登录与鉴权

REST API-- spring boot jwt

Spring Boot# 使用AOP实现接口鉴权访问白名单限制记录接口访问日志限制接口请求次数

Spring Boot# 使用AOP实现接口鉴权访问白名单限制记录接口访问日志限制接口请求次数

Spring Boot @Aspect 切面编程实现访问请求日志记录