JWT和Spring Security集成
通常情况下,把API直接暴露出去是风险很大的,
我们一般需要对API划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户对应的API
(一)JWT是什么,为什么要使用它?
互联网服务离不开用户认证。一般流程是下面这样。
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
3、服务器向用户返回一个 session_id,写入用户的 Cookie。
4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
(引自:阮一峰的网络日志 JSON Web Token 入门教程)
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息
JWT的结构
JWT包含了使用.
分隔的三部分:
Header 头部
Payload 负载
Signature 签名
JWT的工作流程
下面是一个JWT的工作流程图。模拟一下实际的流程是这样的(假设受保护的API在/protected中)
1.用户导航到登录页,输入用户名、密码,进行登录
2.服务器验证登录鉴权,如果用户合法,根据用户的信息和服务器的规则生成JWT Token
3.服务器将该token以json形式返回(不一定要json形式,这里说的是一种常见的做法)
4.用户得到token,存在localStorage、cookie或其它数据存储形式中。
5.以后用户请求/protected中的API时,在请求的header中加入 Authorization: Bearer xxxx(token)。此处注意token之前有一个7字符长度的 Bearer
6.服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
7.用户取得结果
(二)SpringSecurity
Spring Security 是为基于Spring的应用程序提供声明式安全保护的安全性框架。
一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)
两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,
也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。
系统通过校验用户名和密码来完成认证过程。
用户授权指的是验证某个用户是否有权限执行某个操作。
在一个系统中,不同用户所具有的权限是不同的。
比如对一个文件来说,有的用户只能进行读取,
而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,
而每个角色则对应一系列的权限。
对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。
(三)如何利用Spring Security和JWT一起来完成API保护
1.导入依赖
2.配置application.properties
spring.jackson.serialization.indent_output=true //JSON格式化
logging.level.org.springframework.security=info //打印security日志记录
3.新增 AuthorityName + Authority + 修改 Admins
/**
* 角色枚举类
*/
public enum AuthorityName {
ROLE_ADMIN,ROLE_USER
}
import java.io.Serializable; public class Authority implements Serializable {
private Integer id;
private AuthorityName name; public Authority() {
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public AuthorityName getName() {
return name;
} public void setName(AuthorityName name) {
this.name = name;
}
}
import java.io.Serializable;
import java.util.Date;
import java.util.List; public class Admins implements Serializable {
private Integer aid;
private String aname;
private String pwd;
private Integer aexist;
private Integer state;
private Integer doid;
private String by1;
private Date lastPasswordResetDate; public Date getLastPasswordResetDate() {
return lastPasswordResetDate;
} public void setLastPasswordResetDate(Date lastPasswordResetDate) {
this.lastPasswordResetDate = lastPasswordResetDate;
} private List<Authority> authorities; public List<Authority> getAuthorities() {
return authorities;
} public void setAuthorities(List<Authority> authorities) {
this.authorities = authorities;
} public String getBy1() {
return by1;
} public void setBy1(String by1) {
this.by1 = by1;
} public Integer getDoid() {
return doid;
} public void setDoid(Integer doid) {
this.doid = doid;
} public Integer getState() {
return state;
} public void setState(Integer state) {
this.state = state;
} public Integer getAexist() {
return aexist;
} public void setAexist(Integer aexist) {
this.aexist = aexist;
} public Integer getAid() {
return aid;
} public void setAid(Integer aid) {
this.aid = aid;
} public String getAname() {
return aname;
} public void setAname(String aname) {
this.aname = aname;
} public String getPwd() {
return pwd;
} public void setPwd(String pwd) {
this.pwd = pwd;
}
}
4.创建安全服务用户
JwtUser + JwtUserFactory + JwtUserDetailsServiceImpl + JwtAuthenticationResponse
JwtUSer需要实现UserDetails接口,用户实体即为Spring Security所使用的用户
/**
* 安全服务的用户
* 需要实现UserDetails接口,用户实体即为Spring Security所使用的用户
*/
public class JwtUser implements UserDetails { private final Integer id;
private final Integer state;
private final String username;
private final String password;
private final String email;
private final Collection<? extends GrantedAuthority> authorities;
private final boolean enabled;
private final Date lastPasswordResetDate; public JwtUser(Integer id, Integer state, String username, String password, String email, Collection<? extends GrantedAuthority> authorities, boolean enabled, Date lastPasswordResetDate) {
this.id = id;
this.state = state;
this.username = username;
this.password = password;
this.email = email;
this.authorities = authorities;
this.enabled = enabled;
this.lastPasswordResetDate = lastPasswordResetDate;
} public Integer getState() {
return state;
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
} @JsonIgnore
@Override
public String getPassword() {
return password;
} @Override
public String getUsername() {
return username;
} @JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
} @JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
} @JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
} @Override
public boolean isEnabled() {
return this.enabled;
} @JsonIgnore
public Integer getId() {
return id;
} public String getEmail() {
return email;
} @JsonIgnore
public Date getLastPasswordResetDate() {
return lastPasswordResetDate;
}
}
public final class JwtUserFactory { private JwtUserFactory() {
} public static JwtUser create(Admins user){
return new JwtUser(
user.getAid(),
user.getState(),
user.getAname(),
user.getPwd(),
user.getEmail(),
mapToGrandAuthroties(user.getAuthorities()),
user.getAexist()==?true:false,
user.getLastPasswordResetDate()
);
} private static List<GrantedAuthority> mapToGrandAuthroties(List<Authority> authorities) {
return authorities.stream()
.map(authority -> new SimpleGrantedAuthority(authority.getName().name()))
.collect(Collectors.toList()); } }
@Service
public class JwtUserDetailServiceImpl implements UserDetailsService { @Autowired
private AdminsMapper adminsMapper; @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Admins admins = this.adminsMapper.findByUsername(username);
if(admins==null){
throw new UsernameNotFoundException("No User found with UserName :"+username);
}else{
return JwtUserFactory.create(admins);
}
}
}
public class JwtAuthenticationResponse implements Serializable {
private static final long serialVersionUID = 4784951536404964122L;
private final String token; public JwtAuthenticationResponse(String token) {
this.token = token;
} public String getToken() {
return this.token;
}
}
配置 application.properties 支持 mybatis 映射文件 xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
5.创建让Spring控制的安全配置类:WebSecurityConfig
/**
* 安全配置类
*/
@SuppressWarnings("SpringJavaAutowiringInspection")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler; @Autowired
private UserDetailsService userDetailsService; @Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
// 设置 UserDetailsService
.userDetailsService(this.userDetailsService)
// 使用 BCrypt 进行密码的 hash
.passwordEncoder(passwordEncoder());
} /**
* 装载 BCrypt 密码编码器
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} @Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
} /**
* token请求授权
*
* @param httpSecurity
* @throws Exception
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.cors().and() // 跨域 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests()
//.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // allow anonymous resource requests
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll() // Un-secure 登录 验证码
.antMatchers(
"/api/auth/**",
"/api/verifyCode/**",
"/api/global_json"
).permitAll()
// secure other api
.anyRequest().authenticated(); // Custom JWT based security filter
// 将token验证添加在密码验证前面
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); // disable page caching
httpSecurity
.headers()
.cacheControl();
}
}
6.在 XxxController 加一个修饰符 @PreAuthorize("hasRole('ADMIN')") 表示这个资源只能被拥有 ADMIN 角色的用户访问
@RequestMapping(value = "/protectedadmin", method = RequestMethod.GET)
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> getProtectedAdmin() {
return ResponseEntity.ok("Greetings from admin protected method!");
} @RequestMapping(value = "/protecteduser", method = RequestMethod.GET)
@PreAuthorize("hasRole('USER')")
public ResponseEntity<?> getProtectedUser() {
return ResponseEntity.ok("Greetings from user protected method!");
}
最后,除了 /api/auth, /api/verifycode, /api/global_json 外请求其他的路径
访问抛异常: org.springframework.security.access.AccessDeniedException: Access is denied
集成 JWT 和 Spring Security,完成鉴权登录,获取Token
1.pom.xml中新增依赖 jjwt 依赖
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.</version>
</dependency> <!-- https://mvnrepository.com/artifact/com.google.code.findbugs/findbugs -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>findbugs</artifactId>
<version>3.0.</version>
</dependency>
2.application.properties 配置 JWT
3.新建一个filter: JwtAuthenticationTokenFilter :用来验证令牌的是否合法
JwtAuthenticationEntryPoint(替代默认弹出登录页面,返回错误信息)
+ JwtAuthenticationRequest (登录信息封装类)
+JwtTokenUtil(用于生成令牌,验证等等一些操作)
package com.wutongshu.springboot.security.filter; import com.wutongshu.springboot.security.JwtTokenUtil;
import io.jsonwebtoken.ExpiredJwtException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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; /**
* Jwt 过滤器
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private final Log logger = LogFactory.getLog(this.getClass()); @Autowired
private UserDetailsService userDetailsService; @Value("${jwt.header}")
private String tokenHeader; @Value("${jwt.tokenHead}")
private String tokenHead; @Autowired
private JwtTokenUtil jwtTokenUtil; @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
final String requestHeader = request.getHeader(this.tokenHeader);
String authToken = null;
String username = null;
logger.info(requestHeader);
//当前请求中包含令牌
if(requestHeader!=null && requestHeader.startsWith(this.tokenHead)){
authToken = requestHeader.substring(tokenHead.length());
try {
//根据令牌信息获取用户名
username = jwtTokenUtil.getUsernameFromToken(authToken);
}catch (IllegalArgumentException e){
logger.error("an error occured during getting username from the token ",e);
}catch (ExpiredJwtException e){
logger.error("the token is Expried and not invalid anymore",e);
} }else{
logger.error("couldn't find Beared String,will ignore the request");
}
logger.info("checking Authentication with username : " + username);
//
if(username!=null && SecurityContextHolder.getContext().getAuthentication()==null){
UserDetails userDetails = userDetailsService.loadUserByUsername(username
);
if(jwtTokenUtil.validateToken(authToken,userDetails)){
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info("authorication user: "+username+", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request,response);
}
}
package com.wutongshu.springboot.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;
import java.io.IOException;
import java.io.Serializable; /**
* 禁止弹出登录页面,返回错误信息
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { private static final long serialVersionUID = -8970718410437077606L; @Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// This is invoked when user tries to access a secured REST resource without supplying any credentials
// We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
package com.wutongshu.springboot.security; import java.io.Serializable; public class JwtAuthenticationRequest implements Serializable { private String username;
private String password; public JwtAuthenticationRequest() {
} public JwtAuthenticationRequest(String username, String password) {
this.username = username;
this.password = password;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
}
}
package com.wutongshu.springboot.security; import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component; /**
* 工具类
*
*/
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -3301605591108950415L;
@SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")
private Clock clock = DefaultClock.INSTANCE; //从application.properties中获取jwt.secret的值,注入到Secret中
@Value("${jwt.secret}")
private String secret; @Value("${jwt.expiration}")
private Long expiration; //根据token获取username
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
} public Date getIssuedAtDateFromToken(String token) {
return getClaimFromToken(token, Claims::getIssuedAt);
} public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
} public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
} private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
} private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
} private Boolean ignoreTokenExpiration(String token) {
// here you specify tokens, for that the expiration is ignored
return false;
} public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
} private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate); return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
} public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
final Date created = getIssuedAtDateFromToken(token);
return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
&& (!isTokenExpired(token) || ignoreTokenExpiration(token));
} public String refreshToken(String token) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate); final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(createdDate);
claims.setExpiration(expirationDate); return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
} public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getIssuedAtDateFromToken(token);
return (
username.equals(user.getUsername())
&& !isTokenExpired(token)
&& !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
);
} private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration * );
}
}
4.在 WebSecurityConfig 中注入这个filter, 并且配置到 HttpSecurity 中
package com.wutongshu.springboot.security.config; import com.wutongshu.springboot.security.JwtAuthenticationEntryPoint;
import com.wutongshu.springboot.security.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.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; /**
* 安全配置类
*
*
*/
@SuppressWarnings("SpringJavaAutowiringInspection")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler; @Autowired
private UserDetailsService userDetailsService; @Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
// 设置 UserDetailsService
.userDetailsService(this.userDetailsService)
// 使用 BCrypt 进行密码的 hash
.passwordEncoder(passwordEncoder());
} /**
* 装载 BCrypt 密码编码器
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} @Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
} /**
* token请求授权
*
* @param httpSecurity
* @throws Exception
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.cors().and() // 跨域 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests()
//.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // allow anonymous resource requests
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll() // Un-secure 登录 验证码
.antMatchers(
"/api/auth/**",
"/alogin"
).permitAll()
// secure other api
.anyRequest().authenticated(); // Custom JWT based security filter
// 将token验证添加在密码验证前面
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); // disable page caching
httpSecurity
.headers()
.cacheControl();
}
}
最后:
完成鉴权(登录),注册和更新token的功能
.AuthenticationRestController + MethodProtectedRestController + UserRestController
package com.wutongshu.springboot.security.controller; import com.wutongshu.springboot.security.JwtAuthenticationRequest;
import com.wutongshu.springboot.security.JwtAuthenticationResponse;
import com.wutongshu.springboot.security.JwtTokenUtil;
import com.wutongshu.springboot.security.JwtUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController
@RequestMapping("/api")
public class AuthenticationRestController { @Value("${jwt.header}")
private String tokenHeader; @Autowired
private AuthenticationManager authenticationManager; @Autowired
private JwtTokenUtil jwtTokenUtil; @Autowired
private UserDetailsService userDetailsService; @RequestMapping(value = "${jwt.route.authentication.path}", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest) throws AuthenticationException {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
);
// Perform the security
final Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication); // Reload password post-security so we can generate token
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails); // Return the token
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
} @RequestMapping(value = "${jwt.route.authentication.refresh}", method = RequestMethod.GET)
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {
String authToken = request.getHeader(tokenHeader);
final String token = authToken.substring();
String username = jwtTokenUtil.getUsernameFromToken(token);
JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())) {
String refreshedToken = jwtTokenUtil.refreshToken(token);
return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));
} else {
return ResponseEntity.badRequest().body(null);
}
}
}
package com.wutongshu.springboot.security.controller; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/api")
public class MethodProtectedRestController { /**
* This is an example of some different kinds of granular restriction for endpoints. You can use the built-in SPEL expressions
* in @PreAuthorize such as 'hasRole()' to determine if a user has access. Remember that the hasRole expression assumes a
* 'ROLE_' prefix on all role names. So 'ADMIN' here is actually stored as 'ROLE_ADMIN' in database!
**/
@RequestMapping(value = "/protectedadmin", method = RequestMethod.GET)
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> getProtectedAdmin() {
return ResponseEntity.ok("Greetings from admin protected method!");
} @RequestMapping(value = "/protecteduser", method = RequestMethod.GET)
@PreAuthorize("hasRole('USER')")
public ResponseEntity<?> getProtectedUser() {
return ResponseEntity.ok("Greetings from user protected method!");
}
}
package com.wutongshu.springboot.security.controller; import com.wutongshu.springboot.security.JwtTokenUtil;
import com.wutongshu.springboot.security.JwtUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController
@RequestMapping("/api")
public class UserRestController { @Value("${jwt.header}")
private String tokenHeader; @Autowired
private JwtTokenUtil jwtTokenUtil; @Autowired
private UserDetailsService userDetailsService; /**
* 获取授权的用户信息
*
* @param request
* @return
*/
@RequestMapping(value = "/user", method = RequestMethod.GET)
public JwtUser getAuthenticatedUser(HttpServletRequest request) {
String token = request.getHeader(tokenHeader).substring();
String username = jwtTokenUtil.getUsernameFromToken(token);
JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username);
return user;
} }
前台页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"></meta>
<title>JWT Spring Security Demo</title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon"></link>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="../css/bootstrap.min.css"></link>
</head>
<body>
<div class="container">
<h1>JWT Spring Security Demo</h1> <div class="alert alert-danger" id="notLoggedIn">Not logged in!</div> <div class="row">
<div class="col-md-6">
<div class="panel panel-default" id="login">
<div class="panel-heading">
<h3 class="panel-title">Login</h3>
</div>
<div class="panel-body">
<form id="loginForm">
<div class="form-group">
<input type="text" class="form-control" id="exampleInputEmail1" placeholder="username"
required name="username"/>
</div>
<div class="form-group">
<input type="password" class="form-control" id="exampleInputPassword1"
placeholder="password" required name="password">
</div>
<div class="well">
Try one of the following logins
<ul>
<li>admin & admin</li>
<li>user & password</li>
<li>disabled & password</li>
</ul>
</div>
<button type="submit" class="btn btn-default">login</button>
</form>
</div>
</div> <div id="userInfo">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Authenticated user</h3>
</div>
<div class="panel-body">
<div id="userInfoBody"></div>
<button type="button" class="btn btn-default" id="logoutButton">logout</button>
</div>
</div>
</div>
</div> <div class="col-md-6">
<div class="btn-group" role="group" aria-label="..." style="margin-bottom: 16px;">
<button type="button" class="btn btn-default" id="exampleServiceBtn">call user protected service</button>
<button type="button" class="btn btn-default" id="adminServiceBtn">call admin protected service</button>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Response:</h3>
</div>
<div class="panel-body">
<pre id="response"></pre>
</div>
</div>
</div>
</div> <div class="row">
<div id="loggedIn" class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Token information</h3>
</div>
<div class="panel-body" id="loggedInBody"></div>
</div>
</div>
</div>
</div> <div class="modal fade" tabindex="-1" role="dialog" id="loginErrorModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">×</span></button>
<h4 class="modal-title">Login unsuccessful</h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal --> <script src="../js/jquery-3.3.1.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="../js/bootstrap.min.js"></script>
<script src="../js/jwt-decode.min.js"></script>
<script src="client.js"></script>
</body>
</html>
client.js:
$(function () {
// VARIABLES =============================================================
var TOKEN_KEY = "jwtToken"
var $notLoggedIn = $("#notLoggedIn");
var $loggedIn = $("#loggedIn").hide();
var $loggedInBody = $("#loggedInBody");
var $response = $("#response");
var $login = $("#login");
var $userInfo = $("#userInfo").hide(); // FUNCTIONS =============================================================
function getJwtToken() {
return localStorage.getItem(TOKEN_KEY);
} function setJwtToken(token) {
localStorage.setItem(TOKEN_KEY, token);
} function removeJwtToken() {
localStorage.removeItem(TOKEN_KEY);
} function doLogin(loginData) {
$.ajax({
url: "http://127.0.0.1:8087/test/api/auth",
type: "POST",
data: JSON.stringify(loginData),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data, textStatus, jqXHR) {
setJwtToken(data.token);
$login.hide();
$notLoggedIn.hide();
showTokenInformation();
showUserInformation();
},
error: function (jqXHR, textStatus, errorThrown) {
if (jqXHR.status === ) {
$('#loginErrorModal')
.modal("show")
.find(".modal-body")
.empty()
.html("<p>Spring exception:<br>" + jqXHR.responseJSON.exception + "</p>");
} else {
throw new Error("an unexpected error occured: " + errorThrown);
}
}
});
} function doLogout() {
removeJwtToken();
$login.show();
$userInfo
.hide()
.find("#userInfoBody").empty();
$loggedIn.hide();
$loggedInBody.empty();
$notLoggedIn.show();
} function createAuthorizationTokenHeader() {
var token = getJwtToken();
if (token) {
return {"Authorization": "Bearer " + token};
} else {
return {};
}
} function showUserInformation() {
$.ajax({
url: "http://127.0.0.1:8087/test/api/user",
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "json",
headers: createAuthorizationTokenHeader(),
success: function (data, textStatus, jqXHR) {
var $userInfoBody = $userInfo.find("#userInfoBody"); $userInfoBody.append($("<div>").text("Username: " + data.username));
$userInfoBody.append($("<div>").text("Email: " + data.email)); var $authorityList = $("<ul>");
data.authorities.forEach(function (authorityItem) {
$authorityList.append($("<li>").text(authorityItem.authority));
});
var $authorities = $("<div>").text("Authorities:");
$authorities.append($authorityList); $userInfoBody.append($authorities);
$userInfo.show();
}
});
} function showTokenInformation() {
var jwtToken = getJwtToken();
var decodedToken = jwt_decode(jwtToken); $loggedInBody.append($("<h4>").text("Token"));
$loggedInBody.append($("<div>").text(jwtToken).css("word-break", "break-all"));
$loggedInBody.append($("<h4>").text("Token claims")); var $table = $("<table>")
.addClass("table table-striped");
appendKeyValue($table, "sub", decodedToken.sub);
appendKeyValue($table, "aud", decodedToken.aud);
appendKeyValue($table, "iat", decodedToken.iat);
appendKeyValue($table, "exp", decodedToken.exp); $loggedInBody.append($table); $loggedIn.show();
} function appendKeyValue($table, key, value) {
var $row = $("<tr>")
.append($("<td>").text(key))
.append($("<td>").text(value));
$table.append($row);
} function showResponse(statusCode, message) {
$response
.empty()
.text("status code: " + statusCode + "\n-------------------------\n" + message);
} // REGISTER EVENT LISTENERS =============================================================
$("#loginForm").submit(function (event) {
event.preventDefault(); var $form = $(this);
var formData = {
username: $form.find('input[name="username"]').val(),
password: $form.find('input[name="password"]').val()
}; doLogin(formData);
}); $("#logoutButton").click(doLogout); $("#exampleServiceBtn").click(function () {
$.ajax({
url: "http://127.0.0.1:8087/test/api/protecteduser",
type: "GET",
contentType: "application/json; charset=utf-8",
headers: createAuthorizationTokenHeader(),
success: function (data, textStatus, jqXHR) {
showResponse(jqXHR.status, data);
},
error: function (jqXHR, textStatus, errorThrown) {
showResponse(jqXHR.status, errorThrown);
}
});
}); $("#adminServiceBtn").click(function () {
$.ajax({
url: "http://127.0.0.1:8087/test/api/protectedadmin",
type: "GET",
contentType: "application/json; charset=utf-8",
headers: createAuthorizationTokenHeader(),
success: function (data, textStatus, jqXHR) {
showResponse(jqXHR.status, data);
},
error: function (jqXHR, textStatus, errorThrown) {
showResponse(jqXHR.status, errorThrown);
}
});
}); $loggedIn.click(function () {
$loggedIn
.toggleClass("text-hidden")
.toggleClass("text-shown");
}); // INITIAL CALLS =============================================================
if (getJwtToken()) {
$login.hide();
$notLoggedIn.hide();
showTokenInformation();
showUserInformation();
}
});
效果图:
当我们用admin的用户登录时:
当我们用USER权限的用户登录时
logout方法里清空token
其他注意事项:1.每次请求都必须携带头部信息,而且必须是JSON对象类型,也就是
createAuthorizationTokenHeader()方法
2.WebSecurityConfig配置类里的限制路径并不需要拼接properties里的路径,也就是说方法里实际是controller层的路径
JWT和Spring Security集成的更多相关文章
- Spring Security 集成 CAS(基于HTTP协议版本)
Spring Security 集成 CAS(基于HTTP协议版本) 近段时间一直研究Spring Security 集成 CAS,网上资料相关资料也很多,不过大都是基于Https的安全认证;使用ht ...
- 单点登录(SSO)解决方案之 CAS客户端与Spring Security集成
接上篇:单点登录(SSO)解决方案之 CAS服务端数据源设置及页面改造 Spring Security Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制 ...
- spring security集成cas实现单点登录
spring security集成cas 0.配置本地ssl连接 操作记录如下: =====================1.创建证书文件thekeystore ,并导出为thekeystore.c ...
- 使用JWT作为Spring Security OAuth2的token存储
序 Spring Security OAuth2的demo在前几篇文章中已经讲过了,在那些模式中使用的都是RemoteTokenService调用授权服务器来校验token,返回校验通过的用户信息供上 ...
- spring boot+spring security集成以及Druid数据库连接池的问题
贴工程目录,其中bll目录下是service+dao层,common是一些公用的模块及功能类,web是controller层 用到了druid及Redis,工具及配置类目录(本文不介绍如何配置drui ...
- Using JWT with Spring Security OAuth
http://www.baeldung.com/spring-security-oauth-jwt ************************************************** ...
- Spring Security 集成CAS实现单点登录
参考:http://elim.iteye.com/blog/2270446 众所周知,Cas是对单点登录的一种实现.本文假设读者已经了解了Cas的原理及其使用,这些内容在本文将不会讨论.Cas有Ser ...
- 将JWT与Spring Security OAuth结合使用
1.概述 在本教程中,我们将讨论如何使用Spring Security OAuth2实现来使用JSON Web令牌. 我们还将继续构建此OAuth系列的上一篇文章. 2. Maven配置 首先,我们需 ...
- Spring Security OAuth2.0认证授权五:用户信息扩展到jwt
历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...
随机推荐
- 【Oracle】DG中物理备库、快照备库的相互转换
一.物理备库切换快照备库 1. 如果正在运行日志应用,先停止 ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL; 2. 确保数据库为MOUN ...
- WEB笔记-3、盒子模型+定位+显示
3.1 盒子模型 边距控制 margin/padding:上 右 下 左: padding:内容和边距之间的空间 margin:”盒子“外撑开的空间,两个相邻标签外边距会出现重叠和累加的现象, ...
- 复习java第五天(枚举、Annotation(注释) 概述)
一.枚举 传统的方式: •在某些情况下,一个类的对象是有限而且固定的.例如季节类,只能有 4 个对象 •手动实现枚举类: —private 修饰构造器. —属性使用 private final 修饰. ...
- Deutsch lernen (10)
Dieser Weg Dieser Weg wird kein leichter sein. Dieser Weg wird steinig und schwer. Nicht mit vielen ...
- JDK自带工具
工具名称 描述 appletviewer.exe 用于运行并浏览applet小程序. apt.exe 注解处理工具(Annotation Processing Tool),主要用于注解处理. extc ...
- c#动态类型Dynamic
需引用System.Dynamic命名空间 来源:http://www.cnblogs.com/ryanding/archive/2010/12/09/1900106.html dynamic Cus ...
- (转)Arcgis for JS之地图自适应调整
http://blog.csdn.net/gisshixisheng/article/details/42675897 概述:本节讲述的内容为当浏览器大小发生变化或者地图展示区域的大小发生变化时,地图 ...
- [转载]查看Linux系统硬件信息实例详解
linux查看系统的硬件信息,并不像windows那么直观,这里我罗列了查看系统信息的实用命令,并做了分类,实例解说. cpu lscpu命令,查看的是cpu的统计信息. blue@blue-pc:~ ...
- php第十一节课
增删改查 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3 ...
- php第十节课
数据访问 面向对象:类:由众多对象抽象出来的对象:由类实例化出来的,任何东西都可以看做对象来研究定义类:class 类名{ 成员变量 成员方法} 构造函数:写法特殊 执行特殊,对类里面的成员进行初始化 ...