目標html
1.Token鑑權java
2.Restful APIweb
3.Spring Security+JWTspring
開始數據庫
自行新建Spring Boot工程json
引入相關依賴安全
<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類服務器
很是簡單的用戶模型,將權限集成到了用戶類中。session
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類app
需實現對用戶表的增刪改查,此處可採用任意數據庫,具體實現自行編寫。
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
本文轉載自
原文做者:Hackyo