找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 310561|回复: 0

程序员学习不动手怎么可以!SpringBoot2.x开发案例分享

[复制链接]

该用户从未签到

发表于 2020-4-28 13:45:33 | 显示全部楼层 |阅读模式

您需要 登录 才可以下载或查看,没有账号?立即注册

×
有很多朋友跟我说,在网上关于springboot的理论知识,一搜一大堆,而且内容基本差不多,但是就是找不到一个能够自己动手操作一下的案例,观看文档,很快就忘记了,做开发的没有实践怎么可以。于是,整理这份文档,通过写代码的形式带大家学习技术。
觉得写得不错的,欢迎大家点赞+关注,后期会不断更新,如果觉得有问题,欢迎评论区讨论交流
阅读本文需要一定的前后端开发基础,前后端分离已成为互联网项目开发的业界标准使用方式,通过Nginx代理+Tomcat的方式有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客户端,例如:浏览器,小程序,安卓,IOS等等)打下坚实的基础。这个步骤是系统架构从猿进化成人的必经之路。
其核心思想是前端页面通过AJAX调用后端的API接口并使用JSON数据进行交互。
开发者通常使用Servlet、Jsp、Velocity、Freemaker、Thymeleaf以及各种框架模板标签的方式实现前端效果展示。通病就是,后端开发者从后端撸到前端,前端只负责切切页面,修修图,更有甚者,一些团队都没有所谓的前端。
在传统架构模式中,前后端代码存放于同一个代码库中,甚至是同一工程目录下。页面中还夹杂着后端代码。前后端分离以后,前后端分成了两个不同的代码库,通常使用 Vue、React、Angular、Layui等一系列前端框架实现。
回到文章的主题,这里我们使用目前最流行的跨域认证解决方案JSON Web Token(缩写 JWT)
pom.xml引入:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
工具类,签发JWT,可以存储简单的用户基础信息,比如用户ID、用户名等等,只要能识别用户信息即可,重要的角色权限不建议存储:
/**
* JWT加密和解密的工具类
*/
public class JwtUtils {
/**
* 加密字符串 禁泄漏
*/
public static final String SECRET = "e3f4e0ffc5e04432a63730a65f0792b0";
public static final int JWT_ERROR_CODE_NULL = 4000; // Token不存在
public static final int JWT_ERROR_CODE_EXPIRE = 4001; // Token过期
public static final int JWT_ERROR_CODE_FAIL = 4002; // 验证不通过

/**
* 签发JWT
* @param id
* @param subject
* @param ttlMillis
* @return String
*/
public static String createJWT(String id, String subject, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis;
Date now = new Date(nowMillis);
SecretKey secretKey = generalKey;
JwtBuilder builder = Jwts.builder
.setId(id)
.setSubject(subject) // 主题
.setIssuer("爪哇笔记") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate); // 过期时间
}
return builder.compact;
}
/**
* 验证JWT
* @param jwtStr
* @return CheckResult
*/
public static CheckResult validateJWT(String jwtStr) {
CheckResult checkResult = new CheckResult;
Claims claims;
try {
claims = parseJWT(jwtStr);
checkResult.setSuccess(true);
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) {
checkResult.setErrCode(JWT_ERROR_CODE_EXPIRE);
checkResult.setSuccess(false);
} catch (SignatureException e) {
checkResult.setErrCode(JWT_ERROR_CODE_FAIL);

} catch (Exception e) {

}
return checkResult;
}

/**
* 密钥
* @return
*/
public static SecretKey generalKey {
byte encodedKey = Base64.decode(SECRET);
SecretKey key = new SecretKeySpec(encodedKey, 0] encodedKey.length, "AES");
return key;
}

/**
* 解析JWT字符串
* @param jwt
* @return
* @throws Exception Claims
*/
public static Claims parseJWT(String jwt) {

return Jwts.parser
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody;
}
}
验证实体信息:
/**
* 验证信息
*/
public class CheckResult {
private int errCode;

private boolean success;

private Claims claims;

public int getErrCode {
return errCode;
}

public void setErrCode(int errCode) {
this.errCode = errCode;
}

public boolean isSuccess {
return success;
}

public void setSuccess(boolean success) {
this.success = success;
}

public Claims getClaims {
return claims;
}

public void setClaims(Claims claims) {
this.claims = claims;
}
}
拦截访问配置,跨域访问设置以及请求拦截过滤:
/**
* 拦截访问配置
*/
@Configuration
public class SafeConfig implements WebMvcConfigurer {

@Bean
public SysInterceptor myInterceptor{
return new SysInterceptor;
}

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET"] "HEAD"] "POST"] "PUT"] "DELETE"]"OPTIONS")
.allowCredentials(false).maxAge(3600);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
String patterns = new String { "/user/login"]"/*.html"};
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(patterns);
}
}
拦截器统一权限校验:
/**
* 认证拦截器
*/
public class SysInterceptor implements HandlerInterceptor {

private static final Logger logger = LoggerFactory.getLogger(SysInterceptor.class);

@Autowired
private SysUserService sysUserService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler){
if (handler instanceof HandlerMethod){
String authHeader = request.getHeader("token");
if (StringUtils.isEmpty(authHeader)) {
logger.info("验证失败");
print(response,Result.error(JwtUtils.JWT_ERROR_CODE_NULL,"签名验证不存在,请重新登录"));
return false;
}else{
CheckResult checkResult = JwtUtils.validateJWT(authHeader);
if (checkResult.isSuccess) {
/**
* 权限验证
*/
String userId = checkResult.getClaims.getId;
HandlerMethod handlerMethod = (HandlerMethod) handler;
Annotation roleAnnotation= handlerMethod.getMethod.getAnnotation(RequiresRoles.class);
if(roleAnnotation!=null){
String role = handlerMethod.getMethod.getAnnotation(RequiresRoles.class).value;
Logical logical = handlerMethod.getMethod.getAnnotation(RequiresRoles.class).logical;
List<String> list = sysUserService.getRoleSignByUserId(Integer.parseInt(userId));
int count = 0;
for(int i=0;i<role.length;i++){
if(list.contains(role[i,)){
count++;
if(logical==Logical.OR){
continue;
}
}
}

if(count==0){
print(response,Result.error("无权限操作"));
return false;
}
}else{
if(count!=role.length){

return false;
}
}
}
return true;
} else {
switch (checkResult.getErrCode) {
case SystemConstant.JWT_ERROR_CODE_FAIL:
logger.info("签名验证不通过");
print(response,Result.error(checkResult.getErrCode,"签名验证不通过,请重新登录"));
break;
case SystemConstant.JWT_ERROR_CODE_EXPIRE:
logger.info("签名过期");
print(response,Result.error(checkResult.getErrCode,"签名过期,请重新登录"));
break;
default:
break;
}
return false;
}
}
}else{
return true;
}
}
/**
* 打印输出
* @param response
* @param message void
*/
public void print(HttpServletResponse response,Object message){
try {
response.setStatus(HttpStatus.OK.value);
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setHeader("Cache-Control"] "no-cache, must-revalidate");
response.setHeader("Access-Control-Allow-Origin"] "*");
PrintWriter writer = response.getWriter;
writer.write(JSONObject.toJSONString(message));
writer.flush;
writer.close;
} catch (IOException e) {
e.printStackTrace;
}
}
}
配置角色注解,可以直接把安全框架Shiro的拷贝过来,如果有需要,菜单权限也可以配置上:
/**
* 权限注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRoles {

/**
* A single String role name or multiple comma-delimitted role names required in order for the method
* invocation to be allowed.
*/
String value;

/**
* The logical operation for the permission check in case multiple roles are specified. AND is the default
* @since 1.1.0
*/
Logical logical default Logical.OR;
}
模拟演示代码:
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 列表
* @return
*/
@RequestMapping("/list")
@RequiresRoles(value="admin")
public Result list {
return Result.ok("十万亿个用户");
}

/**
* 登录
* @return
*/
@RequestMapping("/login")
public Result login {
/**
* 模拟登录过程并返回token
*/
String token = JwtUtils.createJWT("101"]"爪哇笔记"]1000*60*60);
return Result.ok(token);
}
}
前端请求模拟,发送请求之前在Header中附带token信息,更多代码见源码案例:
function login{
$.ajax({
url : "/user/login"]
type : "post"]
dataType : "json"]
success : function(data) {
if(data.code==0){
$.cookie('token', data.msg);
}
},
error : function(XMLHttpRequest, textStatus, errorThrown) {

}
});
}
function user{
$.ajax({
url : "/user/list"]
type : "post"]
dataType : "json"]

alert(data.msg)
},
beforeSend: function(request) {
request.setRequestHeader("token"] $.cookie('token'));
},

}
});
}
</script>
JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期建议设置的相对短一些。对于一些比较重要的权限,使用时应该再次对用户进行数据库认证。为了减少盗用,JWT强烈建议使用 HTTPS 协议传输。
由于服务器不保存用户状态,因此无法在使用过程中注销某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
回复

使用道具 举报

网站地图|页面地图|文字地图|Archiver|手机版|小黑屋|找资源 |网站地图

GMT+8, 2024-10-6 18:23

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表