zoukankan      html  css  js  c++  java
  • 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。具体请看源码分析。

  • 相关阅读:
    Java8 中的 Optional
    阿里云查看本服务器 公网ip地址 命令
    Linux添加alias简化命令
    kafka consumer 配置详解
    Kafka查看topic、consumer group状态命令
    ConcurrentHashMap源码分析
    HashMap源码分析
    Java使用Aspose组件进行多文档间的转换操作
    个人Vim配置(即vim目录下vimrc_)
    暑假个人小结
  • 原文地址:https://www.cnblogs.com/haoliyou/p/9635697.html
Copyright © 2011-2022 走看看