2021-03-17 学习笔记:springboot+springsecurity+mybatis+JWT+Redis

springboot+springsecurity+mybatis+JWT+Redis 

目录

一、springboot              1.新建项目   2.application.yml的配置   3.写一个小demo  4、druid

三、springsecurity          1.依赖 2.工具类 3.实现springsecurity  4.权限访问控制  5 .jwt生成token的工具类6.springsecurity核心

一、springboot
1.新建项目
 使用idea,jdk选择1.8以上


 各个名字自行命名


添加部分依赖,后面再往pom.xml加入(这里忘记改了,springboot的版本我使用的是1.5.3release版本!)


简单的项目搭建好了,下一步
2.application.yml的配置
数据源
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost/springboot?characterEncoding=utf-8&useSSl=false
    driver-class-name: com.mysql.jdbc.Driver
数据池用了druid,pom.xml加入依赖


   com.alibaba
   druid
   1.1.8

添加mabatis、druid,完整配置:
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost/springboot?characterEncoding=utf-8&useSSl=false
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    #监控统计拦截的filters
    filters: stat,wall,log4j
    #druid配置
    #配置初始化大小/最小/最大
    initialSize: 5
    minIdle: 5
    maxActive: 20
    #获取连接等待超时时间
    maxWait: 60000
    #间隔多久进行一次检测,检测需要关闭的空闲连接
    timeBetweenEvictionRunsMillis: 60000
    #一个连接在池中最小生存的时间
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
    poolPreparedStatements: false
    maxPoolPreparedStatementPerConnectionSize: 20
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties:
      druid:
        stat:
          mergeSql: true
          slowSqlMillis: 5000
 
 
#mybatis是独立节点,需要单独配置
mybatis:
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: com.deceen.demo.entity
  configuration:
    map-underscore-to-camel-case: true
3.写一个小demo
controller层
package com.deceen.demo.controller; 
import com.deceen.demo.entity.DemoEntity;
import com.deceen.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; 
import java.util.List; 
/**
 * @author: zzx
 * @date: 2018/10/25 16:48
 * @description: demo
 */
@RestController
@RequestMapping("/test")//这是目录
public class DemoController {
 
    @Autowired
    private DemoService orderService;
 
    @RequestMapping("/getUser")
    public List getUser(){
        List result = orderService.getUser();
        return result;
    }
 
}
service层
package com.deceen.demo.service; 
import com.deceen.demo.entity.DemoEntity; 
import java.util.List; 
/**
 * @author: zzx
 * @date: 2018/10/25 17:00
 * @description:
 */
public interface DemoService { 
    List getUser(); 
}

service层impl
package com.deceen.demo.service.impl;
 
import com.deceen.demo.dao.DemoMapper;
import com.deceen.demo.entity.DemoEntity;
import com.deceen.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; 
import java.util.List; 
@Service
public class DemoServiceImpl implements DemoService { 
    @Autowired
    private DemoMapper demoMapper; 
    @Override
    public List getUser() {
        return demoMapper.getUser();
    }
}
dao层
package com.deceen.demo.dao;
 
import com.deceen.demo.entity.DemoEntity;
import org.springframework.stereotype.Component;
 import java.util.List; 
@Component
public interface DemoMapper { 
    List getUser(); 
}
entity
package com.deceen.demo.entity; 
import lombok.Data; 
@Data
public class DemoEntity { 
    private Integer id; 
    private Integer age; 
    private String name; 
    private Float height; 
}
 

mapper.xml



 
   
 

(相关的数据库自己随便建,跟DemoEntity字段对上就好了。。。)

访问http://localhost:8080/test/getUser

成功

4、druid参数配置

package com.deceen.common.config; 
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource; 
import javax.sql.DataSource;


@Configuration//明确这是个配置类
@PropertySource(value = "classpath:application.yml")//配置路径
public class DruidConfiguration { 
@Bean(destroyMethod = "close", initMethod = "init")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }


    @Bean
    public ServletRegistrationBean druidStatViewServlet(){
              //org.springframework.boot.context.embedded.ServletRegistrationBean提供类的进行注册.
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*"); 
              //添加初始化参数:initParams
              //白名单:
        servletRegistrationBean.addInitParameter("allow","127.0.0.1");
              //IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
        //servletRegistrationBean.addInitParameter("deny","192.168.1.73");
              //登录查看信息的账号密码.
        servletRegistrationBean.addInitParameter("loginUsername","admin");
        servletRegistrationBean.addInitParameter("loginPassword","123456");
              //是否能够重置数据.
        servletRegistrationBean.addInitParameter("resetEnable","false");
        return servletRegistrationBean;
    }
 
    @Bean
    public FilterRegistrationBean druidStatFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
            //添加过滤规则.
        filterRegistrationBean.addUrlPatterns("/*");
             //添加不需要忽略的格式信息.
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
 
}


访问http://localhost:8080/druid/login.html,账号密码为上面代码设置的

成功

三、springsecurity
1.引入相关依赖


    org.springframework.boot
    spring-boot-starter-security



    com.alibaba
    fastjson
    1.2.36

2.写了几个工具类
ResultEnum
package com.deceen.common.Enums; 
import lombok.Getter;
public enum ResultEnum { 
    SUCCESS(101,"成功"),
    FAILURE(102,"失败"),
    USER_NEED_AUTHORITIES(201,"用户未登录"),
    USER_LOGIN_FAILED(202,"用户账号或密码错误"),
    USER_LOGIN_SUCCESS(203,"用户登录成功"),
    USER_NO_ACCESS(204,"用户无权访问"),
    USER_LOGOUT_SUCCESS(205,"用户登出成功"),
    TOKEN_IS_BLACKLIST(206,"此token为黑名单"),
    LOGIN_IS_OVERDUE(207,"登录已失效"),
    ; 
    private Integer code; 
    private String message; 
    ResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
 
    
    public static ResultEnum parse(int code){
        ResultEnum[] values = values();
        for (ResultEnum value : values) {
            if(value.getCode() == code){
                return value;
            }
        }
        throw  new RuntimeException("Unknown code of ResultEnum");
    }
}


ResultVO
package com.deceen.common.VO;
 
import com.deceen.common.Enums.ResultEnum;
 
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
 
/**
 * @author: zzx
 * @date: 2018/10/15 15:00
 * @description: 返回的格式
 */
 
public final class ResultVO implements Serializable {
 
   private static final long serialVersionUID = 1725159680599612404L;
 
   /**
    * 返回msg,object,以及token
    * 返回的code为默认
    * @param message
    * @param data
    * @param jwtToken
    * @return
    */
   public final static  Map success(String message, Object data,String jwtToken,Boolean success) {
      Map map = new HashMap<>();
      map.put("jwtToken",jwtToken);
      map.put("code", ResultEnum.SUCCESS.getCode());
      map.put("message", message);
      map.put("success",success);
      map.put("data", data);
      return map;
   }
 
   /**
    * 返回object,以及token
    * 返回的msg,code为默认
    * @param data
    * @param jwtToken
    * @return
    */
   public final static  Map success(Object data,String jwtToken) {
      Map map = new HashMap();
      map.put("jwtToken",jwtToken);
      map.put("code", ResultEnum.SUCCESS.getCode());
      map.put("message", ResultEnum.SUCCESS.getMessage());
      map.put("data", data);
      map.put("success",true);
      return map;
   }
 
   /**
    * 返回默认的信息
    * @return
    */
   public final static  Map success() {
      Map map = new HashMap();
      map.put("jwtToken",null);
      map.put("code", ResultEnum.SUCCESS.getCode());
      map.put("message", ResultEnum.SUCCESS.getMessage());
      map.put("data", null);
      map.put("success",true);
      return map;
   }
 
   public final static  Map failure(int code, String message,Object data) {
      Map map = new HashMap();
      map.put("code", code);
      map.put("message", message);
      map.put("data", data);
      map.put("success",false);
      return map;
   }
 
   public final static  Map failure(int code, String message) {
      Map map = new HashMap();
      map.put("code", code);
      map.put("message", message);
      map.put("data", null);
      map.put("success",false);
      return map;
   }
 
   public final static  Map failure(ResultEnum respCode, Object data, Boolean success) {
      return getStringObjectMap(respCode, data,success);
   }
 
   public final static  Map failure(ResultEnum respCode, Boolean success) {
      return getStringObjectMap(respCode,success);
   }
 
   /*
   * 成功返回特定的状态码和信息
   * */
   public final static  Map result(ResultEnum respCode, Object data, Boolean success) {
      return getStringObjectMap(respCode, data,success);
   }
 
   private static  Map getStringObjectMap(ResultEnum respCode, Object data, Boolean success) {
      Map map = new HashMap();
      map.put("code", respCode.getCode());
      map.put("message", respCode.getMessage());
      map.put("data", data);
      map.put("success",success);
      return map;
   }
 
   /*
    * 成功返回特定的状态码和信息
    * */
   public final static  Map result(ResultEnum respCode, Boolean success) {
      return getStringObjectMap(respCode,success);
   }
 
   private static Map getStringObjectMap(ResultEnum respCode, Boolean success) {
      Map map = new HashMap();
      map.put("code", respCode.getCode());
      map.put("message", respCode.getMessage());
      map.put("data", null);
      map.put("success",success);
      return map;
   }
 
   /*
    * 成功返回特定的状态码和信息
    * */
   public final static Map result(ResultEnum respCode, String jwtToken, Boolean success) {
      Map map = new HashMap();
      map.put("jwtToken",jwtToken);
      map.put("code", respCode.getCode());
      map.put("message", respCode.getMessage());
      map.put("data", null);
      map.put("success",success);
      return map;
   }
}
可以看出来,我们后面是会集成jwt的token过来的,看得懂就行,后面根据需要改

下面就是springsecurity核心几个东西啦,具体我还没深入,等后面有时间再写一篇深入一点的(大体分析一下shiro和springsecurity吧。。。)

3.实现springsecurity各个核心接口,处理用户各种状态
实现AuthenticationEntryPoint接口,处理用户未登录
package com.deceen.common.security;
 
import com.alibaba.fastjson.JSON;
import com.deceen.common.Enums.ResultEnum;
import com.deceen.common.VO.ResultVO;
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;
 
/**
 * @author: zzx
 * @date: 2018/10/15 15:04
 * @description: 用户未登录时返回给前端的数据
 */
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
 
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_NEED_AUTHORITIES,false)));
    }
}
实现AccessDeniedHandler接口,处理无权登录的情况
package com.deceen.common.security;
 
import com.alibaba.fastjson.JSON;
import com.deceen.common.Enums.ResultEnum;
import com.deceen.common.VO.ResultVO;
import com.deceen.rehab.user.entity.SelfUserDetails;
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;
 
/**
 * @author: zzx
 * @date: 2018/10/15 16:43
 * @description: 无权访问
 */
@Component
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_NO_ACCESS,false)));
    }
}
实现AuthenticationFailureHandler接口,处理用户登录失败
package com.deceen.common.security;
 
import com.alibaba.fastjson.JSON;
import com.deceen.common.Enums.ResultEnum;
import com.deceen.common.VO.ResultVO;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
/**
 * @author: zzx
 * @date: 2018/10/15 15:31
 * @description: 用户登录失败时返回给前端的数据
 */
@Component
public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
 
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_LOGIN_FAILED,false)));
    }
 
}
实现AuthenticationSuccessHandler接口,处理登录成功的情况
package com.deceen.common.security;
 
import com.alibaba.fastjson.JSON;
import com.deceen.common.Enums.ResultEnum;
import com.deceen.common.VO.ResultVO;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
/**
 * @author: zzx
 * @date: 2018/10/15 16:12
 * @description: 用户登录成功时返回给前端的数据
 */
@Component
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        SelfUserDetails userDetails = (SelfUserDetails) authentication.getPrincipal();
 
        String jwtToken = JwtTokenUtil.generateToken(userDetails.getUsername(), 1500);
 
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_LOGIN_SUCCESS,jwtToken,true)));
    }
}
实现LogoutSuccessHandler接口,处理退出成功
package com.deceen.common.security;
 
import com.alibaba.fastjson.JSON;
import com.deceen.common.Enums.ResultEnum;
import com.deceen.common.VO.ResultVO;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
/**
 * @author: zzx
 * @date: 2018/10/16 9:59
 * @description: 登出成功
 */
@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_LOGOUT_SUCCESS,true)));
    }
 
}
实现UserDetails实现自定义对象
package com.deceen.demo.entity;
 
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
 
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
 
/**
 * @author: zzx
 * @date: 2018/10/15 16:58
 * @description: 定义user对象
 */
public class SelfUserDetails implements UserDetails, Serializable {
    private static final long serialVersionUID = 7171722954972237961L;
 
    private Integer id;
    private String username;
    private String password;
    private Set authorities;
 
    @Override
    public Collection getAuthorities() {
        return this.authorities;
    }
 
    public void setAuthorities(Set authorities) {
        this.authorities = authorities;
    }
 
    @Override
    public String getPassword() { // 最重点Ⅰ
        return this.password;
    }
 
    @Override
    public String getUsername() { // 最重点Ⅱ
        return this.username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
 
    //账号是否过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
 
    //账号是否锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
 
    //账号凭证是否未过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
 
    @Override
    public boolean isEnabled() {
        return true;
    }
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
}
2.权限访问控制
package com.deceen.common.security;
 
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
 
import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.Set;
 
/**
 * @author: zzx
 * @date: 2018/10/16 9:54
 * @description: 权限访问控制
 */
@Component("rbacauthorityservice")
public class RbacAuthorityService {
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
 
        Object userInfo = authentication.getPrincipal();
 
        boolean hasPermission  = false;
 
        if (userInfo instanceof UserDetails) {
 
            String username = ((UserDetails) userInfo).getUsername();
 
            //获取资源
            Set urls = new HashSet();
            // 这些 url 都是要登录后才能访问,且其他的 url 都不能访问!
            urls.add("/demo/**");//application.yml里设置了项目路径,百度一下我就不贴了
            Set set2 = new HashSet();
            Set set3 = new HashSet();
 
            AntPathMatcher antPathMatcher = new AntPathMatcher();
 
            for (String url : urls) {
                if (antPathMatcher.match(url, request.getRequestURI())) {
                    hasPermission = true;
                    break;
                }
            }
 
            return hasPermission;
        } else {
            return false;
        }
    }
}
这里提示一下啊,url记得改

3.jwt生成token的工具类
引入依赖


    io.jsonwebtoken
    jjwt
    0.9.0

工具类
package com.deceen.common.utils;
 
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
 
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.Map;
 
/**
 * @author: zzx
 * @date: 2018/10/16 9:06
 * @description: jwt生成token
 */
public class JwtTokenUtil {
 
    // 寻找证书文件
    private static InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("jwt.jks"); // 寻找证书文件
    private static PrivateKey privateKey = null;
    private static PublicKey publicKey = null;
 
    static { // 将证书文件里边的私钥公钥拿出来
        try {
            KeyStore keyStore = KeyStore.getInstance("JKS"); // java key store 固定常量
            keyStore.load(inputStream, "123456".toCharArray());
            privateKey = (PrivateKey) keyStore.getKey("jwt", "123456".toCharArray()); // jwt 为 命令生成整数文件时的别名
            publicKey = keyStore.getCertificate("jwt").getPublicKey();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 生成token
     * @param subject (主体信息)
     * @param expirationSeconds 过期时间(秒)
     * @param claims 自定义身份信息
     * @return
     */
    public static String generateToken(String subject, int expirationSeconds, Map claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setExpiration(new Date(System.currentTimeMillis() + expirationSeconds * 1000))
//                .signWith(SignatureAlgorithm.HS512, salt) // 不使用公钥私钥
                .signWith(SignatureAlgorithm.RS256, privateKey)
                .compact();
    }
 
    /**
     * @author: zzx
     * @date: 2018-10-19 09:10
     * @deprecation: 解析token,获得subject中的信息
    */
    public static String parseToken(String token, String salt) {
        String subject = null;
        try {
            /*Claims claims = Jwts.parser()
//                    .setSigningKey(salt) // 不使用公钥私钥
                    .setSigningKey(publicKey)
                    .parseClaimsJws(token).getBody();*/
            subject = getTokenBody(token).getSubject();
        } catch (Exception e) {
        }
        return subject;
    }
 
    //获取token自定义属性
    public static Map getClaims(String token){
        Map claims = null;
        try {
            claims = getTokenBody(token);
        }catch (Exception e) {
        }
 
        return claims;
    }
 
    // 是否已过期
    public static boolean isExpiration(String token){
        return getTokenBody(token).getExpiration().before(new Date());
    }
 
    private static Claims getTokenBody(String token){
        return Jwts.parser()
                .setSigningKey(publicKey)
                .parseClaimsJws(token)
                .getBody();
    }
}
 
在这里展开一下,为了实现前后端交互,采用了jjwt的方案,后面会加入更多的token验证,现在先把基本的东西弄出来。

大体是思路就是,每次登陆成功会返回token给前端做本地保存,以后每一次前端请求api都会在请求头中带上这个token,我们后面加入一个过滤器,专门拦截token然后验证。肯定会有人说token暴露的问题,我的解决方案很简单,实现一个黑名单,每一次登出或失效的token都加入黑名单(这一块我用redis实现,用其他缓存数据库都行,就是一个思路的问题)。token生成的时候也会在redis加入相应刷新时间和失效时间(例如:7天免登陆,即在7天内会自动刷新用户的token;而失效时间定为十五分钟,即每个token只有15分钟有效时间,过了这个时间,会去判断是否在刷新时间内,如果是,则refresh token,并set进request的请求头之中)

参考链接:

https://www.cnblogs.com/stulzq/p/9678501.html#commentform(记得看评论哦,评论里也有很多精华)

https://segmentfault.com/a/1190000013151506(语言不同,但是思想想通)

 

jwt拦截器
package com.deceen.common.filters;
 
import com.deceen.common.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
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 javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
/**
 * @author: zzx
 * @date: 2018/10/15 17:30
 * @description: 确保在一次请求只通过一次filter,而不需要重复执行
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    SelfUserDetailsService userDetailsService;
 
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
 
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String authToken = authHeader.substring("Bearer ".length());
 
            String username = JwtTokenUtil.parseToken(authToken, "_secret");
 
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
 
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (userDetails != null) {
                    UsernamePasswordAuthenticationToken authentication =
                                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
 
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
 
        filterChain.doFilter(request, response);
    }
}
文中的SelfUserDetailsService接下来继续

文中的jwt.jks是jwt证书,你可以自己生成,也可以用我的项目中的,我放到github里,在resource里找

 

4.springsecurity核心处理
继承UserDetailsService,用户认证的业务代码
package com.deceen.demo.service;
 
import com.deceen.demo.entity.SelfUserDetails;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Component;
 
import java.util.HashSet;
import java.util.Set;
 
/**
 * @author: zzx
 * @date: 2018/10/15 16:54
 * @description: 用户认证、权限
 */
@Component
public class SelfUserDetailsService implements UserDetailsService {
 
    @Autowired
    private UserMapper userMapper;
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
        //通过username查询用户
        SelfUserDetails user = userMapper.getUser(username);
        if(user == null){
            //仍需要细化处理
            throw new UsernameNotFoundException("该用户不存在");
        }
 
        Set authoritiesSet = new HashSet();
        // 模拟从数据库中获取用户角色
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");
 
        authoritiesSet.add(authority);
        user.setAuthorities(authoritiesSet);
 
        log.info("用户{}验证通过",username);
        return user;
    }
}
相应dao层
package com.deceen.demo.dao;
 
import com.deceen.demo.entity.SelfUserDetails;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
 
/**
 * @author: zzx
 * @date: 2018/10/18 14:59
 * @description: 用户dao层
 */
@Component
public interface UserMapper {
 
    //通过username查询用户
    SelfUserDetails getUser(@Param("username") String username);
 
}
相应mapper.xml



 
   
 

核心处理类
package com.deceen.common.config;
 
import com.deceen.common.filters.JwtAuthenticationTokenFilter;
import com.deceen.common.security.*;
import com.deceen.demo.service.SelfUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
/**
 * @author: zzx
 * @date: 2018/10/15 16:47
 * @description:
 */
@Configuration
public class SpringSecurityConf extends WebSecurityConfigurerAdapter {
 
 
    @Autowired
    AjaxAuthenticationEntryPoint authenticationEntryPoint;//未登陆时返回 JSON 格式的数据给前端(否则为 html)
 
    @Autowired
    AjaxAuthenticationSuccessHandler authenticationSuccessHandler; //登录成功返回的 JSON 格式数据给前端(否则为 html)
 
    @Autowired
    AjaxAuthenticationFailureHandler authenticationFailureHandler; //登录失败返回的 JSON 格式数据给前端(否则为 html)
 
    @Autowired
    AjaxLogoutSuccessHandler logoutSuccessHandler;//注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
 
    @Autowired
    AjaxAccessDeniedHandler accessDeniedHandler;//无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
 
    @Autowired
    SelfUserDetailsService userDetailsService; // 自定义user
 
    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 加入自定义的安全认证
//        auth.authenticationProvider(provider);
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
 
        // 去掉 CSRF
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 使用 JWT,关闭token
                .and()
 
                .httpBasic().authenticationEntryPoint(authenticationEntryPoint)
 
                .and()
                .authorizeRequests()//定义哪些URL需要被保护、哪些不需要被保护
 
                .anyRequest()//任何请求,登录后可以访问
                .access("@rbacauthorityservice.hasPermission(request,authentication)") // RBAC 动态 url 认证
 
                .and()
                .formLogin()  //开启登录, 定义当需要用户登录时候,转到的登录页面
//                .loginPage("/test/login.html")
//                .loginProcessingUrl("/login")
                .successHandler(authenticationSuccessHandler) // 登录成功
                .failureHandler(authenticationFailureHandler) // 登录失败
                .permitAll()
 
                .and()
                .logout()//默认注销行为为logout
                .logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll();
 
        // 记住我
        http.rememberMe().rememberMeParameter("remember-me")
                .userDetailsService(userDetailsService).tokenValiditySeconds(1000);
 
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
 
    }
}
有个点说一下:我密码加密选择了BCryptPasswordEncoder格式,官方也推荐这个,然后你们可以自行建一个测试用户,记得密码先用BCryptPasswordEncoder加密一下哦(写一个createUser方法,密码加密保存)

测试
postman模拟请求:

登录成功:

复制token,如下操作,进行接口测试:

请求接口成功:

 

下一篇讲解集成redis+token刷新+logback,有兴趣就接着看吧

本篇文章的代码:https://github.com/zzxzzxhao/springboot-springsecurity

springboot+springsecurity+mybatis+JWT+Redis 实现前后端离(实战篇续)

 

 

后记:
1.加入简单注册功能,SpringSecurityConf加入配置放开url,DemoController加入如下代码

/**
 * 简单注册功能
 * @param username
 * @param password
 * @return
 */
@PostMapping("/register")
public Map register(String username,String password){
    orderService.register(username,password);
    return ResultVO.result(ResultEnum.SUCCESS,true);
}
(详细代码我上传到github了)

2.关于mysql新版本,导致mysql-connector-java版本对应不上的问题

mysql版本:

pom.xml更新对应mysql-connector-java版本


    mysql
    mysql-connector-java
    8.0.11

同时配置文件中关于数据库连接地址需要加上&serverTimezone=GMT%2B8,即:

spring:
  datasource:
    username: root
    password: 123
    url: jdbc:mysql://localhost/springboot?characterEncoding=utf-8&useSSl=false&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.jdbc.Driver
3.关于自定义登录的

其实也很简单,但是我就不上传了,简单说一下spring security流程

这个流程很清楚啦

UsernamePasswordAuthenticationToken封装加密后的密码以及用户信息
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, bCryptPasswordEncoder.encode(password));
try {
    Authentication authentication = authenticationManager.authenticate(authenticationToken);
    SecurityContextHolder.getContext().setAuthentication(authentication);
    //具体实现。。。
} catch (AuthenticationException e) {
    //自行处理
}
 

你可能感兴趣的:(spring,boot)