写在开头:这篇是实战篇,即默认各位看官具备相应的基础
目录
一、springboot
1.新建项目
2.application.yml的配置
3.写一个小demo
二、druid
三、springsecurity
1.引入相关依赖
2.写了几个工具类
3.实现springsecurity各个核心接口,处理用户各种状态
2.权限访问控制
3.jwt生成token的工具类
4.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加入依赖
#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();
}
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;
/**
-
@author: zzx
-
@date: 2018/10/25 17:03
-
@description:
*/
@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;
/**
-
@author: zzx
-
@date: 2018/10/25 17:05
-
@description:
*/
@Component
public interface DemoMapper {List getUser();
}
entity
package com.deceen.demo.entity;
import lombok.Data;
/**
-
@author: zzx
-
@date: 2018/10/25 17:01
-
@description:
*/
@Data
public class DemoEntity {private Integer id;
private Integer age;
private String name;
private Float height;
}
这里介绍一下lombok,感觉蛮好用的一个插件,DemoEntity这个类中的@Data注解就是其一,可以自动生成getset方法(编译的时候),后面也会用到。教程:https://blog.youkuaiyun.com/zhglance/article/details/54931430
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?><select id="getUser" resultType="com.deceen.demo.entity.DemoEntity">
SELECT * FROM girl
</select>
(相关的数据库自己随便建,跟DemoEntity字段对上就好了。。。)
访问http://localhost:8080/test/getUser
成功
二、druid
上文提到druid,其实尚未真正可以使用,下面我们继续
1.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;
/**
-
druid参数配置
-
@author zzx
-
@date 2018/10/15 10:09
*/
@Configuration
@PropertySource(value = “classpath:application.yml”)
public class DruidConfiguration {/**
- @author zzx
- @date 2018-10-15 11:28
- @todo 数据源配置
*/
@Bean(destroyMethod = “close”, initMethod = “init”)
@ConfigurationProperties(prefix = “spring.datasource”)
public DataSource druidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
/**
-
druid
-
注册一个StatViewServlet
-
@return
/
@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;
}
/**
- druid过滤器
- 注册一个:filterRegistrationBean
- @return
/
@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;
/**
-
@author: zzx
-
@date: 2018/10/15 15:16
-
@description: 返回的错误码枚举类
*/
@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;
}/**
- @author: zzx
- @date: 2018-10-15 16:26
- @deprecation:通过code返回枚举
*/
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<String, Object> success(String message, Object data,String jwtToken,Boolean success) {
Map<String, Object> 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<String, Object> success(Object data,String jwtToken) {
Map<String, Object> map = new HashMap<String, Object>();
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<String, Object> success() {
Map<String, Object> map = new HashMap<String, Object>();
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<String, Object> failure(int code, String message,Object data) {
Map<String, Object> map = new HashMap<String, Object>();
map.put(“code”, code);
map.put(“message”, message);
map.put(“data”, data);
map.put(“success”,false);
return map;
}
public final static Map<String, Object> failure(int code, String message) {
Map<String, Object> map = new HashMap<String, Object>();
map.put(“code”, code);
map.put(“message”, message);
map.put(“data”, null);
map.put(“success”,false);
return map;
}
public final static Map<String, Object> failure(ResultEnum respCode, Object data, Boolean success) {
return getStringObjectMap(respCode, data,success);
}
public final static Map<String, Object> failure(ResultEnum respCode, Boolean success) {
return getStringObjectMap(respCode,success);
}
/*
- 成功返回特定的状态码和信息
- */
public final static Map<String, Object> result(ResultEnum respCode, Object data, Boolean success) {
return getStringObjectMap(respCode, data,success);
}
private static Map<String, Object> getStringObjectMap(ResultEnum respCode, Object data, Boolean success) {
Map<String, Object> map = new HashMap<String, Object>();
map.put(“code”, respCode.getCode());
map.put(“message”, respCode.getMessage());
map.put(“data”, data);
map.put(“success”,success);
return map;
}
/*
* 成功返回特定的状态码和信息
* */
public final static Map<String, Object> result(ResultEnum respCode, Boolean success) {
return getStringObjectMap(respCode,success);
}
private static Map<String, Object> getStringObjectMap(ResultEnum respCode, Boolean success) {
Map<String, Object> map = new HashMap<String, Object>();
map.put(“code”, respCode.getCode());
map.put(“message”, respCode.getMessage());
map.put(“data”, null);
map.put(“success”,success);
return map;
}
/*
* 成功返回特定的状态码和信息
* */
public final static Map<String, Object> result(ResultEnum respCode, String jwtToken, Boolean success) {
Map<String, Object> map = new HashMap<String, Object>();
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<? extends GrantedAuthority> authorities;@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}public void setAuthorities(Set<? extends GrantedAuthority> 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<String> 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<String,Object> 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<String,Object> getClaims(String token){
Map<String,Object> 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
<select id="getUser" parameterType="String" resultType="com.deceen.demo.entity.SelfUserDetails">
SELECT * FROM user
where username = #{username}
</select>
核心处理类 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<String, Object> 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) {
//自行处理
}
作者:一点点前进的小郑
来源:优快云
原文:https://blog.youkuaiyun.com/zzxzzxhao/article/details/83381876
版权声明:本文为博主原创文章,转载请附上博文链接!