目标

1.Token鉴权

2.Restful API

3.Spring Security+JWT

开始

自行新建Spring Boot工程

引入相关依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>1.5.9.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>

User类

非常简单的用户模型,将权限集成到了用户类中。

pacage com.domain
/**
* 用户模型
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
public class User { private String id;
private String username;
private String password;
private List<String> roles; ......
省略get、set方法
...... }

IUserRepository类

需实现对用户表的增删改查,此处可采用任意数据库,具体实现自行编写。

package com.dao
/**
* 用户表操作接口
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
@Component
public interface IUserRepository{ /**
* 通过用户名查找用户
*
* @param username 用户名
* @return 用户信息
*/
User findByUsername(String username); }

JwtUser类

安全模块的用户模型

package com.security;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; /**
* 安全用户模型
*
* @author hackyo
* Created on 2017/12/8 9:20.
*/
public class JwtUser implements UserDetails { private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities; JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
} @Override
public String getUsername() {
return username;
} @JsonIgnore
@Override
public String getPassword() {
return password;
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
} @JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
} @JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
} @JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
} @JsonIgnore
@Override
public boolean isEnabled() {
return true;
} }

JwtTokenUtil类

Token工具类

这里设置了密钥为aaaaaaaa,有效期为2592000秒

package com.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component; import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; /**
* JWT工具类
*
* @author hackyo
* Created on 2017/12/8 9:20.
*/
@Component
public class JwtTokenUtil implements Serializable { /**
* 密钥
*/
private final String secret = "aaaaaaaa"; /**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + 2592000L * 1000);
return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
} /**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
} /**
* 生成令牌
*
* @param userDetails 用户
* @return 令牌
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(2);
claims.put("sub", userDetails.getUsername());
claims.put("created", new Date());
return generateToken(claims);
} /**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
} /**
* 判断令牌是否过期
*
* @param token 令牌
* @return 是否过期
*/
public Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
return false;
}
} /**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put("created", new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
} /**
* 验证令牌
*
* @param token 令牌
* @param userDetails 用户
* @return 是否有效
*/
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
String username = getUsernameFromToken(token);
return (username.equals(user.getUsername()) && !isTokenExpired(token));
} }

JwtUserDetailsServiceImpl类

用户验证方法类

package com.security;

import com.safepass.dao.IUserRepository;
import com.safepass.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
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.Service; import java.util.stream.Collectors; /**
* 用户验证方法
*
* @author hackyo
* Created on 2017/12/8 9:18.
*/
@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService { private IUserRepository userRepository; @Autowired
public JwtUserDetailsServiceImpl(IUserRepository userRepository) {
this.userRepository = userRepository;
} @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
} else {
return new JwtUser(user.getUsername(), user.getPassword(), user.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
}
} }

JwtAuthenticationTokenFilter类

Token过滤器实现

package com.security;

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.core.userdetails.UserDetailsService;
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; /**
* Token过滤器
*
* @author hackyo
* Created on 2017/12/8 9:28.
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private UserDetailsService userDetailsService;
private JwtTokenUtil jwtTokenUtil; @Autowired
public JwtAuthenticationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
} @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String tokenHead = "Bearer ";
if (authHeader != null && authHeader.startsWith(tokenHead)) {
String authToken = authHeader.substring(tokenHead.length());
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
} }

EntryPointUnauthorizedHandler类

自定义了身份验证失败的返回值

package com.security;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; /**
* 自定401返回值
*
* @author hackyo
* Created on 2017/12/9 20:10.
*/
@Component
public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint { @Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(401);
} }

RestAccessDeniedHandler类

自定了权限不足的返回值

package com.security;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* 自定403返回值
*
* @author hackyo
* Created on 2017/12/9 20:10.
*/
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler { @Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(403);
} }

WebSecurityConfig类

安全配置类

这里设置了禁止访问所有地址,除了用于验证身份的/user/**地址

同时密码的加密方式为BCrypt

package com.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /**
* 安全模块配置
*
* @author hackyo
* Created on 2017/12/8 9:15.
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private UserDetailsService userDetailsService;
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
private RestAccessDeniedHandler restAccessDeniedHandler;
private PasswordEncoder passwordEncoder; @Autowired
public WebSecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter, EntryPointUnauthorizedHandler entryPointUnauthorizedHandler, RestAccessDeniedHandler restAccessDeniedHandler) {
this.userDetailsService = userDetailsService;
this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
this.entryPointUnauthorizedHandler = entryPointUnauthorizedHandler;
this.restAccessDeniedHandler = restAccessDeniedHandler;
this.passwordEncoder = new BCryptPasswordEncoder();
} @Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder);
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/user/**").permitAll()
.anyRequest().authenticated()
.and().headers().cacheControl();
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler).accessDeniedHandler(restAccessDeniedHandler); } }

IUserService类

定义用户的基本操作

package com.service;

import com.domain.User;

/**
* 用户操作接口
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
public interface IUserService { /**
* 用户登录
*
* @param username 用户名
* @param password 密码
* @return 操作结果
*/
String login(String username, String password); /**
* 用户注册
*
* @param user 用户信息
* @return 操作结果
*/
String register(User user); /**
* 刷新密钥
*
* @param oldToken 原密钥
* @return 新密钥
*/
String refreshToken(String oldToken); }

UserServiceImpl类

IUserService的实现类,注册时会将用户权限设置为ROLE_USER,同时将密码使用BCrypt加密

package com.service.impl;

import com.dao.IUserRepository;
import com.domain.User;
import com.security.JwtTokenUtil;
import com.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service; import java.util.ArrayList;
import java.util.List; /**
* 用户操作接口实现
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
@Service
public class UserServiceImpl implements IUserService { private AuthenticationManager authenticationManager;
private UserDetailsService userDetailsService;
private JwtTokenUtil jwtTokenUtil;
private IUserRepository userRepository; @Autowired
public UserServiceImpl(AuthenticationManager authenticationManager, UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, IUserRepository userRepository) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.userRepository = userRepository;
} @Override
public String login(String username, String password) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return jwtTokenUtil.generateToken(userDetails);
} @Override
public String register(User user) {
String username = user.getUsername();
if (userRepository.findByUsername(username) != null) {
return "用户已存在";
}
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = user.getPassword();
user.setPassword(encoder.encode(rawPassword));
List<String> roles = new ArrayList<>();
roles.add("ROLE_USER");
user.setRoles(roles);
userRepository.insert(user);
return "success";
} @Override
public String refreshToken(String oldToken) {
String token = oldToken.substring("Bearer ".length());
if (!jwtTokenUtil.isTokenExpired(token)) {
return jwtTokenUtil.refreshToken(token);
}
return "error";
} }

UserController类

控制器,控制访问

package com.controller;

import com.domain.User;
import com.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.*; /**
* 用户管理Controller
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
@CrossOrigin
@RestController
@RequestMapping(value = "/user", produces = "text/html;charset=UTF-8")
public class UserController { private IUserService userService; @Autowired
public UserController(IUserService userService) {
this.userService = userService;
} /**
* 用户登录
*
* @param username 用户名
* @param password 密码
* @return 操作结果
* @throws AuthenticationException 错误信息
*/
@PostMapping(value = "/login", params = {"username", "password"})
public String getToken(String username, String password) throws AuthenticationException {
return userService.login(username, password);
} /**
* 用户注册
*
* @param user 用户信息
* @return 操作结果
* @throws AuthenticationException 错误信息
*/
@PostMapping(value = "/register")
public String register(User user) throws AuthenticationException {
return userService.register(user);
} /**
* 刷新密钥
*
* @param authorization 原密钥
* @return 新密钥
* @throws AuthenticationException 错误信息
*/
@GetMapping(value = "/refreshToken")
public String refreshToken(@RequestHeader String authorization) throws AuthenticationException {
return userService.refreshToken(authorization);
} }

 

使用

只需要在方法或类上加注解即可实现账号控制

例如,我们想控制该方法只允许用户本人使用,#号表示方法的参数,可以在参数中加上@P('name')来指定名称,同时也可直接使用模型,如user.username等

总之,其中可以写入任何Spring EL

@PreAuthorize("#username == authentication.name")
@GetMapping(value = "/getInfo")
public String getInfo(String username) {
return JSON.toJSONString(userService.getInfo(username));
}

另外也可以自定义控制注解,使用@PostFilter注解,并实现hasPermission类即可,同时需要在WebSecurityConfigurerAdapter中开启。

测试

运行程序后,我们使用Postman进行测试

1.注册

URL:http://localhost:8080/user/register

参数:username、password

返回success即为成功

 2.登录

URL:http://localhost:8080/user/login

参数:username、password

可以看到服务器将我们的Token返回了

 3.刷新Token

URL(GET方法):http://localhost:8080/user/refreshToken

参数:在Header中加入登录时返回的Token,注意,需要在Token前加上“Bearer ”,最后有个空格

Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE1MTMzMTE1NjMsInN1YiI6IjEyMyIsImNyZWF0ZWQiOjE1MTI3MDY3NjM3NjB9.baiY8QcbJgq4FQMC2piN1smbW57WjDDTiRVIL9hJeC_DcPgcyJweWqkS6g7825mPKFlByuUx7XN8nUOIszDVcw

可以看到服务器给我们返回了新的Token,如果我们不加上Token的话,将无法访问

参考:

  http://www.jianshu.com/p/6307c89fe3fa

  http://www.jianshu.com/p/4468a2fff879

Spring Boot中使用使用Spring Security和JWT的更多相关文章

  1. Spring Boot中使用Spring Security进行安全控制

    我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面.要实现访问控制的方法多种多样,可以通过Aop.拦截器实现,也可以通过框架实现(如:Apache ...

  2. Spring Boot中的静态资源文件

    Spring Boot中的静态资源文件 1.SSM中的配置 2.Spring Boot 中的配置 2.1 整体规划 2.2 源码解读 2.3 自定义配置 2.3.1 application.prope ...

  3. Spring Boot中使用 Spring Security 构建权限系统

    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,为应用系统提供声明式的安全 ...

  4. Spring Boot中集成Spring Security 专题

    check to see if spring security is applied that the appropriate resources are permitted: @Configurat ...

  5. Spring Boot 中使用 Spring Security, OAuth2 跨域问题 (自己挖的坑)

    使用 Spring Boot 开发 API 使用 Spring Security + OAuth2 + JWT 鉴权,已经在 Controller 配置允许跨域: @RestController @C ...

  6. Spring Boot中Starter是什么

    比如我们要在Spring Boot中引入Web MVC的支持时,我们通常会引入这个模块spring-boot-starter-web,而这个模块如果解压包出来会发现里面什么都没有,只定义了一些POM依 ...

  7. 在spring boot中使用webSocket组件(二)

    该篇演示如何使用websocket创建一对一的聊天室,废话不多说,我们马上开始! 一.首先先创建前端页面,代码如下图所示: 1.login.html <!DOCTYPE html> < ...

  8. Spring Boot 中的同一个 Bug,竟然把我坑了两次!

    真是郁闷,不过这事又一次提醒我解决问题还是要根治,不能囫囵吞枣,否则相同的问题可能会以不同的形式出现,每次都得花时间去搞.刨根问底,一步到位,再遇到类似问题就可以分分钟解决了. 如果大家没看过松哥之前 ...

  9. spring boot rest 接口集成 spring security(2) - JWT配置

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

随机推荐

  1. U盘启动盘还原

    cmd运行 diskpart list disk clean 一般都是disk 1,不过最好先list查一下 右击桌面上的计算机图标,选择管理,进入磁盘管理,能看到u盘分区是未分配的(黑色),右击,新 ...

  2. HAOI2018 简要题解

    这套题是 dy, wearry 出的.学长好强啊,可惜都 \(wc\) 退役了.. 话说 wearry 真的是一个计数神仙..就没看到他计不出来的题...每次考他模拟赛总有一两道毒瘤计数TAT 上午的 ...

  3. 【原创】hdu 1166 敌兵布阵(线段树→单点更新,区间查询)

    学习线段树的第三天...真的是没学点啥好的,又是一道水题,纯模板,我个人觉得我的线段树模板还是不错的(毕竟我第一天相当于啥都没学...找了一整天模板,对比了好几个,终于找到了自己喜欢的类型),中文题目 ...

  4. github上的面试库

    https://yuchengkai.cn/docs/zh/frontend/#%E5%86%85%E7%BD%AE%E7%B1%BB%E5%9E%8B https://github.com/taiz ...

  5. HDU5909Tree Cutting

    题目大意 给定一颗树,每个点有点权,问对于每个m,有多少个联通块的权值异或和为m. 题解 解法1:可以考虑树形dp,设dp[u][i]表示以u为根的子树中u必须选,联通块权值异或值为i的联通块个数. ...

  6. centos7使用yum安装zabbix3.4

     应用环境: 关于ZABBIX: zabbix一开强大的开源监控工具,同类型还有nagios,ganglia,cacti等监控工具,通过过去一年Google趋势就知道zabbix全球的相对热度:   ...

  7. CSharp for Jupyter Notebook

    之前说有机会就说下Linux下如何搭建C#版的交互编程,今天写篇文章还债^_^ Win下比较简单,可以自己看官方文档,下面逆天带大家搭建下Linux下的环境(官方方法有问题) 在线预览:https:/ ...

  8. css 圆形头像

    方法一:背景图片(推荐) 好处是,图片长宽不等的情况下图片不会变形 .ui-photo { width: 100px; height: 100px; background: url("img ...

  9. 洛谷P4390 Mokia CDQ分治

    喜闻乐见的CDQ分治被我搞的又WA又T..... 大致思路是这样的:把询问用二维前缀和的思想拆成4个子询问.然后施CDQ大法即可. 我却灵光一闪:树状数组是可以求区间和的,那么我们只拆成两个子询问不就 ...

  10. CF1101

    D:题意:树上每个顶点有个权值,求最长链,满足链上gcd > 1 解:对每个质数建虚树,每个点只会拆成log个点,所以是log2的. #include <bits/stdc++.h> ...