一般狀況下,把API直接暴露出去是風險很大的,css
咱們通常須要對API劃分出必定的權限級別,而後作一個用戶的鑑權,依據鑑權結果給予用戶對應的APIhtml
互聯網服務離不開用戶認證。通常流程是下面這樣。java
一、用戶向服務器發送用戶名和密碼。jquery
二、服務器驗證經過後,在當前對話(session)裏面保存相關數據,好比用戶角色、登陸時間等等。web
三、服務器向用戶返回一個 session_id,寫入用戶的 Cookie。ajax
四、用戶隨後的每一次請求,都會經過 Cookie,將 session_id 傳回服務器。spring
五、服務器收到 session_id,找到前期保存的數據,由此得知用戶的身份。數據庫
這種模式的問題在於,擴展性(scaling)很差。單機固然沒有問題,若是是服務器集羣,或者是跨域的服務導向架構,就要求 session 數據共享,每臺服務器都可以讀取 session。express
舉例來講,A 網站和 B 網站是同一家公司的關聯服務。如今要求,用戶只要在其中一個網站登陸,再訪問另外一個網站就會自動登陸,請問怎麼實現?apache
一種解決方案是 session 數據持久化,寫入數據庫或別的持久層。各類服務收到請求後,都向持久層請求數據。這種方案的優勢是架構清晰,缺點是工程量比較大。另外,持久層萬一掛了,就會單點失敗。
另外一種方案是服務器索性不保存 session 數據了,全部數據都保存在客戶端,每次請求都發回服務器。JWT 就是這種方案的一個表明。
(引自:阮一峯的網絡日誌 JSON Web Token 入門教程)
JSON Web Token(JWT)是一個很是輕巧的規範。這個規範容許咱們使用JWT在用戶和服務器之間傳遞安全可靠的信息
JWT包含了使用.
分隔的三部分:
Header 頭部
Payload 負載
Signature 簽名
下面是一個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.用戶取得結果
Spring Security 是爲基於Spring的應用程序提供聲明式安全保護的安全性框架。
通常來講,Web 應用的安全性包括用戶認證(Authentication)和用戶受權(Authorization)
兩個部分。用戶認證指的是驗證某個用戶是否爲系統中的合法主體,
也就是說用戶可否訪問該系統。用戶認證通常要求用戶提供用戶名和密碼。
系統經過校驗用戶名和密碼來完成認證過程。
用戶受權指的是驗證某個用戶是否有權限執行某個操做。
在一個系統中,不一樣用戶所具備的權限是不一樣的。
好比對一個文件來講,有的用戶只能進行讀取,
而有的用戶能夠進行修改。通常來講,系統會爲不一樣的用戶分配不一樣的角色,
而每一個角色則對應一系列的權限。
對於上面提到的兩種應用情景,Spring Security 框架都有很好的支持。
spring.jackson.serialization.indent_output=true //JSON格式化 logging.level.org.springframework.security=info //打印security日誌記錄
/** * 角色枚舉類 */ 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; } }
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()==1?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; } }
/** * 安全配置類 */ @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(); } }
@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
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.google.code.findbugs/findbugs --> <dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>findbugs</artifactId> <version>3.0.1</version> </dependency>
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 * 1000); } }
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(7); 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(7); 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 === 401) { $('#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層的路徑