主要實現 Spring Security
的安全認證,結合 RESTful API
的風格,使用無狀態的環境。css
主要實現是經過請求的 URL ,經過過濾器來作不一樣的受權策略操做,爲該請求提供某個認證的方法,而後進行認證,受權成功返回受權實例信息,供服務調用。html
基於Token的身份驗證的過程以下:java
每一次請求都須要token,因此每次請求都會去驗證用戶身份,因此這裏必需要使用緩存,mysql
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
public interface UserDetails extends Serializable {
//返回分配給用戶的角色列表
Collection<? extends GrantedAuthority> getAuthorities();
//返回密碼
String getPassword();
//返回賬號
String getUsername();
// 帳戶是否未過時
boolean isAccountNonExpired();
// 帳戶是否未鎖定
boolean isAccountNonLocked();
// 密碼是否未過時
boolean isCredentialsNonExpired();
// 帳戶是否激活
boolean isEnabled();
}
// 根據用戶名查找用戶的信息
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
咱們只要實現這個擴展,就可以自定義方式獲取認證的基本信息web
WebSecurityConfigurerAdapter
提供了一種便利的方式去建立 WebSecurityConfigurer
的實例,只須要重寫 WebSecurityConfigurerAdapter
的方法,便可配置攔截什麼URL、設置什麼權限等安全控制。算法
下面是主要會是要到的幾個配置:spring
/** * 主要是對身份認證的設置 * @param auth * @throws Exception */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
/** * 複寫這個方法來配置 {@link HttpSecurity}. * 一般,子類不能經過調用 super 來調用此方法,由於它可能會覆蓋其配置。 默認配置爲: * */
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
/** * Override this method to configure {@link WebSecurity}. For example, if you wish to * ignore certain requests. * 主要是對某些 web 靜態資源的設置 */
public void configure(WebSecurity web) throws Exception {
}
閱讀源碼瞭解。sql
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 判斷是不是須要驗證方法(是不是登錄的請求),不是的話直接放過
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
// 登錄的請求開始進行驗證
Authentication authResult;
try {
// 開始認證,attemptAuthentication在 UsernamePasswordAuthenticationFilter 中實現
authResult = attemptAuthentication(request, response);
// return null 認證失敗
if (authResult == null) {
return;
}
// 篇幅問題,中間不少代碼刪了
successfulAuthentication(request, response, chain, authResult);
}
// 接收並解析用戶登錄信息,爲已驗證的用戶返回一個已填充的身份驗證令牌,表示成功的身份驗證,
// 若是身份驗證過程失敗,就拋出一個AuthenticationException
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 方法將 request 中的 username 和 password 生成 UsernamePasswordAuthenticationToken 對象,用於 AuthenticationManager 的驗證
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
驗證 Authentication 對象(裏面包含着驗證對象)數據庫
最後它調用的是 Authentication result = provider.authenticate(authentication);
json
只要咱們自定義 AuthenticationProvider
就能完成自定義認證。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 50)
private String username;
@Column(nullable = false)
private String password;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createDate;
@OneToMany(targetEntity = UserRole.class, mappedBy = "userId", fetch = FetchType.EAGER) // mappedBy 只有在雙向關聯的時候設置,表示關係維護的一端,不然會生成中間表A_B
@org.hibernate.annotations.ForeignKey(name = "none") // 注意這裏不能使用 @JoinColumn 否則會生成外鍵
private Set<UserRole> userRoles;
}
@Entity
@Data
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String name;
}
@Entity
@Data
public class UserRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 50, nullable = false)
private Long userId;
@ManyToOne(targetEntity = Role.class)
@JoinColumn(name = "roleId", nullable = false, foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))
private Role role;
}
認證流程:
我使用的是服務端無狀態的token 交換的形式,因此引用的是 jwt,首先實現 jwt:
# jwt 配置
jwt:
# 加密密鑰
secret: 61D73234C4F93E03074D74D74D1E39D9 #blog.wuwii.com
# token有效時長
expire: 7 # 7天,單位天
# token 存在 header 中的參數
header: token
@ConfigurationProperties(prefix = "jwt")
@Data
public class JwtUtil {
/** * 密鑰 */
private String secret;
/** * 有效期限 */
private int expire;
/** * 存儲 token */
private String header;
/** * 生成jwt token * * @param username * @return token */
public String generateToken(String username) {
Date nowDate = new Date();
return Jwts.builder()
.setHeaderParam("typ", "JWT")
// 後續獲取 subject 是 username
.setSubject(username)
.setIssuedAt(nowDate)
.setExpiration(DateUtils.addDays(nowDate, expire))
// 這裏我採用的是 HS512 算法
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/** * 解析 token, * 利用 jjwt 提供的parser傳入祕鑰, * * @param token token * @return 數據聲明 Map<String, Object> */
private Claims getClaimByToken(String token) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
return null;
}
}
/** * token是否過時 * * @return true:過時 */
public boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
public String getUsernameFromToken(String token) {
if (StringUtils.isBlank(token)) {
throw new KCException("無效 token", HttpStatus.UNAUTHORIZED.value());
}
Claims claims = getClaimByToken(token);
if (claims == null || isTokenExpired(claims.getExpiration())) {
throw new KCException(header + "失效,請從新登陸", HttpStatus.UNAUTHORIZED.value());
}
return claims.getSubject();
}
}
public class UserDetailsImpl implements UserDetails {
private User user;
public UserDetailsImpl(User user) {
this.user = user;
}
/** * 獲取權限信息 * @return */
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<UserRole> userRoles = user.getUserRoles();
List<GrantedAuthority> auths = new ArrayList<>(userRoles.size());
userRoles.parallelStream().forEach(userRole -> {
// 默認ROLE_ 爲前綴,能夠更改
auths.add(new SimpleGrantedAuthority("ROLE_" + userRole.getRole().getName()));
});
return auths;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 帳戶是否未過時
@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;
}
}
@Slf4j
@CacheConfig(cacheNames = "users")
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
@Cacheable
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Username is not valid.");
}
log.debug("The User is {}", user);
return SecurityModelFactory.create(user);
}
}
轉換 UserDetails 的工廠類
public class SecurityModelFactory {
public static UserDetails create(User user) {
return new UserDetailsImpl(user);
}
}
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
private JwtUtil jwtUtil;
/** * 過濾,我目前使用的是默認的,能夠本身看源碼按需求更改 */
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// todo 在這裏能夠按需求進行過濾,根據源碼來修改擴展很是方便
super.doFilter(request, response, chain);
}
/** * 若是須要進行登錄認證,會在這裏進行預處理 */
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// todo 在登錄認證的時候,能夠作些其餘的驗證操做,好比驗證碼
return super.attemptAuthentication(request, response);
}
/** * 登錄成功調用,返回 token */
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException {
String token = jwtUtil.generateToken(authResult.getName());
response.setStatus(HttpStatus.OK.value());
response.getWriter().print(token);
}
}
doFilter
方法中,這裏能夠自定義定義過濾;登錄
的請求,會進入 attemptAuthentication
組裝登錄信息,而且進行登錄認證;successfulAuthentication
方法。@Slf4j
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
/** * 驗證登陸信息,若登錄成功,設置 Authentication */
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
UserDetails user = userDetailsService.loadUserByUsername(username);
if (passwordEncoder.matches(password, user.getPassword())) {
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(username, password, authorities);
}
throw new BadCredentialsException("The password is not correct.");
}
/** * 當前 Provider 是否支持對該類型的憑證提供認證服務 */
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}
咱們本身定義的 AuthenticationProvider
主要是實現前面通過過濾器封裝的認證對象 UsernamePasswordAuthenticationToken
進行解析認證,
若是認證成功 就給改 UsernamePasswordAuthenticationToken
設置對應的權限,而後返回 Authentication
Authentication
,失敗拋出異常。/** * token 校驗 * BasicAuthenticationFilter 濾器負責處理任何具備HTTP請求頭的請求的請求, * 以及一個基本的身份驗證方案和一個base64編碼的用戶名:密碼令牌。 */
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/** * 在此方法中檢驗客戶端請求頭中的token, * 若是存在併合法,就把token中的信息封裝到 Authentication 類型的對象中, * 最後使用 SecurityContextHolder.getContext().setAuthentication(authentication); 改變或刪除當前已經驗證的 pricipal * * @param request * @param response * @param chain * @throws IOException * @throws ServletException */
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = request.getHeader(jwtUtil.getHeader());
//判斷是否有token
if (token == null) {
chain.doFilter(request, response);
return;
}
// 經過token 獲取帳戶信息,而且存入到將身份信息存放在安全系統的上下文。
UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
chain.doFilter(request, response);
}
/** * 解析token中的信息 */
private UsernamePasswordAuthenticationToken getAuthentication(String token) {
String username = jwtUtil.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null, userDetails.getAuthorities());
}
return null;
}
}
doFilterInternal
方法中,對請求是否帶token
進行判斷,自定義配置 Spring Security 配置類 WebSecurityConfig
,進項相關配置,而且將所須要的類注入到系統中。
@Configuration
@EnableWebSecurity // 開啓 Security
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
//jsr250Enabled有三種註解,分別是@RolesAllowed,@PermitAll,@DenyAll,功能跟名字同樣,
// securedEnabled 開啓註解
// prePostEnabled 相似用的最多的是 @PreAuthorize
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public JwtUtil jwtUtil() {
return new JwtUtil();
}
/** * 注入 LoginFilter 時候須要,注入 authenticationManager */
@Bean
public LoginFilter loginFilter() throws Exception {
LoginFilter loginFilter = new LoginFilter();
loginFilter.setAuthenticationManager(authenticationManager());
return loginFilter;
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
return new JwtAuthenticationFilter(authenticationManager());
}
@Bean
public UserDetailsService customService() {
return new UserDetailServiceImpl();
}
/** * 認證 AuthenticationProvider */
@Bean
public AuthenticationProvider authenticationProvider() {
return new CustomAuthenticationProvider();
}
/** * BCrypt算法免除存儲salt * BCrypt算法將salt隨機並混入最終加密後的密碼,驗證時也無需單獨提供以前的salt,從而無需單獨處理salt問題。 * @return */
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(5);
}
/** * 主要是對身份驗證的設置 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
// 注入身份的 Bean
.authenticationProvider(authenticationProvider())
.userDetailsService(userDetailsService())
// 默認登錄的加密,自定義登錄的時候無效
.passwordEncoder(passwordEncoder());
// 在內存中設置固定的帳戶密碼以及身份信息
/*auth .inMemoryAuthentication().withUser("user").password("password").roles("USER").and() .withUser("admin").password("password").roles("USER", "ADMIN");*/
}
/** * * @param http * @throws Exception */
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 關閉 csrf
.csrf().disable()
// 設置 session 狀態 STATELESS 無狀態
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 須要權限驗證
.mvcMatchers("/user/**").authenticated()
.and()
// 登錄頁面
.formLogin()
//.loginPage("/login.html")
// 登錄成功跳轉頁面
.defaultSuccessUrl("/")
//.failureForwardUrl("/login.html")
.permitAll()
.and()
// 登出
//.logout()
// 註銷的時候刪除會話
//.deleteCookies("JSESSIONID")
// 默認登出請求爲 /logout,能夠用下面自定義
//.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
// 自定義登出成功的頁面,默認爲登錄頁
//.logoutSuccessUrl("/logout.html")
//.permitAll()
//.and()
// 開啓 cookie 保存用戶信息
//.rememberMe()
// cookie 有效時間
//.tokenValiditySeconds(60 * 60 * 24 * 7)
// 設置cookie 的私鑰,默認爲隨機生成的key
//.key("remember")
//.and()
//驗證登錄的 filter
.addFilter(loginFilter())
//驗證token的 filter
.addFilter(jwtAuthenticationFilter());
}
/** * Web層面的配置,通常用來配置無需安全檢查的路徑 * @param web * @throws Exception */
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(
"**.js",
"**.css",
"/images/**",
"/webjars/**",
"/**/favicon.ico"
);
}
}
@RestController
@RequestMapping(value = "/user", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@PreAuthorize("hasRole('USER')")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
@PreAuthorize("hasRole('admin')")
public ResponseEntity<List<UserVO>> getAllUser() {
List<User> users = userService.findAll();
List<UserVO> userViews = userService.castUserVO(users);
return ResponseEntity.ok(userViews);
}
}
請求上面的getAllUser
方法,須要當前用戶同時擁有 ROLE_USER
和 ROLE_admin
兩個權限,才能經過權限驗證。
在 @PreAuthorize 中咱們能夠利用內建的 SPEL 表達式:好比 ‘hasRole()’ 來決定哪些用戶有權訪問。需注意的一點是 hasRole 表達式認爲每一個角色名字前都有一個前綴 ‘ROLE_’。
後來,我發現進行用戶認證的時候,會將全部的 provider 都嘗試一遍,那麼外面將登錄的 UsernameAndPasswordToken
和 JwtTToken
均可以分別進行驗證進行了啊,全部我預先定義 UsernamePasswordAuthenticationToken
包裝登錄的信息,而後進入登錄的 AuthenticationProvider
進行認證,token
驗證形式,使用 PreAuthenticatedAuthenticationToken
的包裝,而後進入例外一個 `AuthenticationProvider
` 中認證。
如今咱們的流程就更加清晰了。
因此如今我對之前的權限配置以及認證進行了一些更改:
在這裏,我根據不一樣請求的類型,進行不一樣的適配,而後進行加工分裝成不一樣的認證憑證,而後根據憑證的不一樣,進行不一樣的認證。
@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
if (isLoginRequest(httpRequest, httpResponse)) {
Authentication authResult = processLogin(httpRequest, httpResponse);
successfulAuthentication(httpRequest, httpResponse, chain, authResult);
return;
}
String token = obtainToken(httpRequest);
if (StringUtils.isNotBlank(token)) {
processTokenAuthentication(token);
}
} catch (AuthenticationException e) {
unsuccessfulAuthentication(httpRequest, httpResponse, e);
return;
}
chain.doFilter(request, response);
}
/** * 登錄成功調用,返回 token */
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException {
String token = jwtUtil.generateToken(authResult.getName());
response.setStatus(HttpStatus.OK.value());
response.getWriter().print(token);
}
private boolean isLoginRequest(HttpServletRequest request, HttpServletResponse response) {
return requiresAuthentication(request, response) && "POST".equalsIgnoreCase(request.getMethod());
}
private String obtainToken(HttpServletRequest request) {
return request.getHeader(jwtUtil.getHeader());
}
private Authentication processLogin(HttpServletRequest request, HttpServletResponse response) {
String username = obtainUsername(request);
String password = obtainPassword(request);
return tryAuthenticationWithUsernameAndPassword(username, password);
}
private void processTokenAuthentication(String token) {
Authentication resultOfAuthentication = tryToAuthenticateWithToken(token);
// 設置上下文用戶信息以及權限
SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
}
private Authentication tryAuthenticationWithUsernameAndPassword(String username, String password) {
Authentication authentication = new UsernamePasswordAuthenticationToken(username, password);
return tryToAuthenticate(authentication);
}
private Authentication tryToAuthenticateWithToken(String token) {
PreAuthenticatedAuthenticationToken requestAuthentication = new PreAuthenticatedAuthenticationToken(token, null);
return tryToAuthenticate(requestAuthentication);
}
private Authentication tryToAuthenticate(Authentication requestAuth) {
Authentication responseAuth = getAuthenticationManager().authenticate(requestAuth);
if (responseAuth == null || !responseAuth.isAuthenticated()) {
throw new InternalAuthenticationServiceException("Unable to authenticate User for provided credentials");
}
log.debug("User successfully authenticated");
return responseAuth;
}
}
根據提供的憑證的類型,進行相關的驗證操做
跟上個版本的 登錄驗證中的 CustomAuthenticationProvider
代碼同樣實現同樣。
根據 token 查找它的 權限 信息,並裝在到認證的憑證中。
public class TokenAuthenticateProvider implements AuthenticationProvider {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = authentication.getName();
String username = jwtUtil.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return new PreAuthenticatedAuthenticationToken(username, null, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return PreAuthenticatedAuthenticationToken.class.equals(authentication);
}
}
和上個版本沒什麼變化,只是將類換了一下
@Configuration
@EnableWebSecurity // 開啓 Security
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public JwtUtil jwtUtil() {
return new JwtUtil();
}
@Bean
public UserDetailsService customService() {
return new UserDetailServiceImpl();
}
@Bean("loginAuthenticationProvider")
public AuthenticationProvider loginAuthenticationProvider() {
return new LoginAuthenticationProvider();
}
@Bean("tokenAuthenticationProvider")
public AuthenticationProvider tokenAuthenticationProvider() {
return new TokenAuthenticateProvider();
}
@Bean
public AuthenticationFilter authenticationFilter() throws Exception {
AuthenticationFilter authenticationFilter = new AuthenticationFilter();
authenticationFilter.setAuthenticationManager(authenticationManager());
return authenticationFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(5);
}
@Bean
@Override
public UserDetailsService userDetailsService() {
return new UserDetailServiceImpl();
}
/** * 主要是對身份驗證的設置 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(loginAuthenticationProvider())
.authenticationProvider(tokenAuthenticationProvider())
.userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 關閉 csrf
.csrf().disable()
// 設置 session 狀態 STATELESS 無狀態
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 須要權限驗證
.mvcMatchers("/user/**").authenticated()
.and()
// 登錄頁面
.formLogin()
//.loginPage("/login.html")
// 登錄成功跳轉頁面
.defaultSuccessUrl("/")
.failureForwardUrl("/login.html")
.permitAll()
.and()
.addFilter(authenticationFilter())
;
}
}
方法 | 說明 |
---|---|
openidLogin() |
用於基於 OpenId 的驗證 |
headers() |
將安全標頭添加到響應 |
cors() |
配置跨域資源共享( CORS ) |
sessionManagement() |
容許配置會話管理 |
portMapper() |
容許配置一個PortMapper (HttpSecurity#(getSharedObject(class)) ),其餘提供SecurityConfigurer 的對象使用 PortMapper 從 HTTP 重定向到 HTTPS 或者從 HTTPS 重定向到 HTTP。默認狀況下,Spring Security使用一個PortMapperImpl 映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443 |
jee() |
配置基於容器的預認證。 在這種狀況下,認證由Servlet容器管理 |
x509() |
配置基於x509的認證 |
rememberMe |
容許配置「記住我」的驗證 |
authorizeRequests() |
容許基於使用HttpServletRequest 限制訪問 |
requestCache() |
容許配置請求緩存 |
exceptionHandling() |
容許配置錯誤處理 |
securityContext() |
在HttpServletRequests 之間的SecurityContextHolder 上設置SecurityContext 的管理。 當使用WebSecurityConfigurerAdapter 時,這將自動應用 |
servletApi() |
將HttpServletRequest 方法與在其上找到的值集成到SecurityContext 中。 當使用WebSecurityConfigurerAdapter 時,這將自動應用 |
csrf() |
添加 CSRF 支持,使用WebSecurityConfigurerAdapter 時,默認啓用 |
logout() |
添加退出登陸支持。當使用WebSecurityConfigurerAdapter 時,這將自動應用。默認狀況是,訪問URL」/ logout」,使HTTP Session無效來清除用戶,清除已配置的任何#rememberMe() 身份驗證,清除SecurityContextHolder ,而後重定向到」/login?success」 |
anonymous() |
容許配置匿名用戶的表示方法。 當與WebSecurityConfigurerAdapter 結合使用時,這將自動應用。 默認狀況下,匿名用戶將使用org.springframework.security.authentication.AnonymousAuthenticationToken 表示,幷包含角色 「ROLE_ANONYMOUS」 |
formLogin() |
指定支持基於表單的身份驗證。若是未指定FormLoginConfigurer#loginPage(String) ,則將生成默認登陸頁面 |
oauth2Login() |
根據外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份驗證 |
requiresChannel() |
配置通道安全。爲了使該配置有用,必須提供至少一個到所需信道的映射 |
httpBasic() |
配置 Http Basic 驗證 |
addFilterAt() |
在指定的Filter類的位置添加過濾器 |