再写一个例子,该例中,假设
/api需要登录且具有有效token才能访问的接口。
/home是公开的接口,无须登录授权均可访问。
/login是登录接口,用户通过/login提交用户名和密码进行鉴权验证。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@RestController
public class MyController {
@Autowired
private AuthenticationManager authenticationManager;
//@Autowired
//private MyUserDetailsService userDetailsService;
@RequestMapping("/api")
private String api(@RequestHeader Map<String, String> headers) {
System.out.println("headers: " + headers);
String token_header = headers.get("authorization");
System.out.println("token: " + token_header);
// token一般又称之为"Bearer token",以Bearer开头
// 截取纯粹的token
String BEARER = "Bearer ";
String token = token_header.substring(BEARER.length());//从Bearer 之后开始截取
String username = JwtTokenUtil.getUsername(token);
String role = JwtTokenUtil.getUserRole(token);
return username + " - " + role + " @api";
}
/**
* 这里的MyRequest封装了用户登录需要填写上报的信息。也可以通过把用户名和密码写入http中的header实现上报。
*
* @param request
* @param response
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
private
//ResponseEntity<?>
void login(@RequestBody MyRequest request, HttpServletResponse response) {
System.out.println("/login");
String username = request.getUsername();
String password = request.getPassword();
System.out.println("用户名:" + username);
System.out.println("密码:" + password);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password);
authenticationManager.authenticate(authentication);
//UserDetails userDetails = userDetailsService.loadUserByUsername(username);
String token = JwtTokenUtil.createToken(username, "ADMIN");
//return ResponseEntity.ok(token);
// 设置编码
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
// 请求头里返回token
// "Bearer "前缀token
response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);
response.setContentType("text/json;charset=utf-8");
try {
response.getWriter().write("登录成功");
} catch (Exception e) {
e.printStackTrace();
}
}
@RequestMapping(value = "/home")
private String home() {
return "home";
}
}
token工具类:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtTokenUtil {
private static final String USER_NAME = "username";
// Token头
public static final String TOKEN_HEADER = "Authorization";
// Token前缀
public static final String TOKEN_PREFIX = "Bearer ";
// 签名主题
public static final String SUBJECT = "zhangphil";
// 过期时间
public static final long EXPIRITION = 3 * 60 * 1000;
// 应用密钥
public static final String APPSECRET_KEY = "zhangphil_secret";
// 角色权限声明
private static final String ROLE_CLAIMS = "role";
/**
* 生成Token
*/
public static String createToken(String username, String role) {
Map<String, Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
String token = Jwts
.builder()
.setSubject(SUBJECT)
.setClaims(map)
.claim(USER_NAME, username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
.signWith(SignatureAlgorithm.HS256, APPSECRET_KEY)
.compact();
return token;
}
/**
* 校验Token
*/
public static Claims checkJWT(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(APPSECRET_KEY)
.parseClaimsJws(token)
.getBody();
return claims;
} catch (Exception e) {
e.printStackTrace();
}
return claims;
}
/**
* 从Token中提取username
*/
public static String getUsername(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
return claims.get(USER_NAME).toString();
}
/**
* 从Token中获取用户角色
*/
public static String getUserRole(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
return claims.get("role").toString();
}
/**
* 校验Token是否过期
*/
public static boolean isExpiration(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
return claims.getExpiration().before(new Date());
}
}
token工具类中使用的类需要在pom.xml添加引用:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
权限异常处理:
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/javascript;charset=utf-8");
response.getWriter().print("没有访问权限");
}
}
用户登录鉴权过滤类:
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
public class MyBasicAuthenticationFilter extends BasicAuthenticationFilter {
public MyBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* 过滤http请求
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println(getClass().getSimpleName() + " doFilterInternal");
String tokenHeader = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
// 请求头中没Authorization或是Authorization不以Bearer开头
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
chain.doFilter(request, response);
System.out.println("token为空,或者token头部不是以Bearer 开头");
return;
}
// 若请求头中有token
// 设置认证信息
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
super.doFilterInternal(request, response, chain);
}
/**
* 从token中获取用户信息并新建一个token。
*
* @param tokenHeader 字符串形式的Token请求头
* @return 带用户名和密码以及权限的Authentication
*/
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
// 去掉前缀,获取Token字
String token = tokenHeader.replace(JwtTokenUtil.TOKEN_PREFIX, "");
// 从Token中获取用户名
String username = JwtTokenUtil.getUsername(token);
// 从Token中解密获取用户角色
String role = JwtTokenUtil.getUserRole(token);
// 将[ROLE_XXX,ROLE_YYY]格式角色字符串转换为数组
String[] roles = StringUtils.strip(role, "[]").split(", ");
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (String s : roles) {
authorities.add(new SimpleGrantedAuthority(s));
}
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
return null;
}
}
用于用户登录密码编解码处理的类:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
/**
* 注意,此处是明文,实际场景时候要加密
*/
@Component
public class MyPasswordEncoder extends BCryptPasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword为空!");
}
if (encodedPassword == null || encodedPassword.length() == 0) {
throw new IllegalArgumentException("encodedPassword为空");
}
return encodedPassword.equals(rawPassword);
}
}
封装了用户(浏览器或客户端)发送请求的数据:
import java.io.Serializable;
public class MyRequest implements Serializable {
private String username;
private String password;
//JSON Parsing
public MyRequest() {
}
public MyRequest(String username, String password) {
this.setUsername(username);
this.setPassword(password);
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
用户数据获取类,该类通常用于后端从后台的数据库根据用户名获取该用户的信息,供上层程序逻辑使用:
import org.springframework.security.core.userdetails.User;
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.ArrayList;
@Service
public class MyUserDetailsService implements UserDetailsService {
//假设已经知道用户名和密码,硬编码写死了用户名和密码
//省去从数据库读取用户信息的过程
public static final String USER_NAME = "zhangphil";
public static final String USER_PASSWORD = "12345678";//正常情况密码应该加密,而不是明文。此处仅做演示。
//数据库读写
//@Autowired
//private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println(username + " 加载信息");
if (USER_NAME.equals(username)) {
//正常情况下,这里应该从数据库中,根据传入的用户名把用户信息读出来,然后返回User
//从数据库中查用户
//User user = userMapper.findUserByUsername(username);
//简单演示期间,这里省略读数据库过程。
return new User(USER_NAME, USER_PASSWORD, new ArrayList<>());
} else {
throw new UsernameNotFoundException("用户不存在:" + username);
}
}
}
这里将为登录成功的用户返回正确的token:
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
/**
* 验证用户名和密码
* 验证通过后,生成token返回给客户端
*/
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public MyUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* 验证
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
System.out.println("attemptAuthentication");
// 从输入流中获取登录信息
// 创建token调用authenticationManager.authenticate() 后,
// 让Spring security框架内部验证
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
request.getParameter("username"),
request.getParameter("password")));
}
/**
* 验证成功,生成token返回给客户端
*/
@Override
public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
System.out.println("successfulAuthentication ...");
User user = (User) authResult.getPrincipal();
// 从User中获取权限信息
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
// 创建Token
String token = JwtTokenUtil.createToken(user.getUsername(), authorities.toString());
// 防乱码
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
// 在请求头返回token
// 请求头带有"Bearer "前缀的token
response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);
// 防乱码
response.setContentType("text/json;charset=utf-8");
response.getWriter().write("登录成功");
System.out.println("登录成功");
}
/**
* 验证失败
*/
@Override
public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
String retData;
if (failed instanceof AccountExpiredException) {
retData = "账号过期";
} else if (failed instanceof BadCredentialsException) {
retData = "密码错误";
} else if (failed instanceof CredentialsExpiredException) {
retData = "密码过期";
} else if (failed instanceof DisabledException) {
retData = "账号不可用";
} else if (failed instanceof LockedException) {
retData = "账号锁定";
} else if (failed instanceof InternalAuthenticationServiceException) {
retData = "内部授权服务异常";
} else {
retData = "未知异常";
}
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(retData);
}
}
以上各个组件、工具类的“总装车间”,组合上面的组件,形成一台能够运行的token认证鉴权系统:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.builders.WebSecurity;
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.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService userDetailsService;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(new MyPasswordEncoder());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
//login为鉴权登录,排除授权拦截
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
// 登录拦截
.addFilter(new MyBasicAuthenticationFilter(authenticationManagerBean()))
// JWT鉴权拦截
.addFilter(new MyUsernamePasswordAuthenticationFilter(authenticationManagerBean()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
// 用户访问无权限资源异常处理
.authenticationEntryPoint(new MyAuthenticationEntryPoint());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 假设这一部分接口是公开开放的,不需要token即可访问。
* 这部分客户端http请求不拦截
* 排除。
*/
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
"/login**",
"/home**");
}
}
最后是一个很常规、简单的application启动加载器(入口Main“函数”):
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringTokenApplication {
public static void main(String[] args) {
SpringApplication.run(SpringTokenApplication.class, args);
}
}
启动SpringTokenApplication,/home无需鉴权认证可以直接访问:
/login提交用户名和密码:
登录成功后,后台系统返回token:
在/api接口的header里面填入token,获取接口数据:
后端系统正确返回/api接口数据: