01-整合Spring Security
一冷溃、Spring Security介紹
1、框架介紹
Spring?是一個非常流行和成功的Java應(yīng)用開發(fā)框架振惰。Spring Security?基于?Spring?框架松申,提供了一套?Web?應(yīng)用安全性的完整解決方案检眯。一般來說凿叠,Web?應(yīng)用的安全性包括用戶認證(Authentication)和用戶授權(quán)(Authorization)兩個部分涩笤。
(1)用戶認證指的是:驗證某個用戶是否為系統(tǒng)中的合法主體,也就是說用戶能否訪問該系統(tǒng)盒件。用戶認證一般要求用戶提供用戶名和密碼蹬碧。系統(tǒng)通過校驗用戶名和密碼來完成認證過程。
(2)用戶授權(quán)指的是驗證某個用戶是否有權(quán)限執(zhí)行某個操作炒刁。在一個系統(tǒng)中恩沽,不同用戶所具有的權(quán)限是不同的。比如對一個文件來說切心,有的用戶只能進行讀取,而有的用戶可以進行修改片吊。一般來說绽昏,系統(tǒng)會為不同的用戶分配不同的角色,而每個角色則對應(yīng)一系列的權(quán)限俏脊。
Spring Security其實就是用filter全谤,多請求的路徑進行過濾。
(1)如果是基于Session爷贫,那么Spring-security會對cookie里的sessionid進行解析认然,找到服務(wù)器存儲的sesion信息,然后判斷當(dāng)前用戶是否符合請求的要求漫萄。
(2)如果是token卷员,則是解析出token,然后將當(dāng)前請求加入到Spring-security管理的權(quán)限信息中去
2腾务、認證與授權(quán)實現(xiàn)思路
如果系統(tǒng)的模塊眾多毕骡,每個模塊都需要就行授權(quán)與認證,所以我們選擇基于token的形式進行授權(quán)與認證,用戶根據(jù)用戶名密碼認證成功未巫,然后獲取當(dāng)前用戶角色的一系列權(quán)限值窿撬,并以用戶名為key,權(quán)限列表為value的形式存入redis緩存中叙凡,根據(jù)用戶名相關(guān)信息生成token返回劈伴,瀏覽器將token記錄到cookie中,每次調(diào)用api接口都默認將token攜帶到header請求頭中握爷,Spring-security解析header頭獲取token信息跛璧,解析token獲取當(dāng)前用戶名,根據(jù)用戶名就可以從redis中獲取權(quán)限列表饼拍,這樣Spring-security就能夠判斷當(dāng)前請求是否有權(quán)限訪問
二赡模、整合Spring Security
1、在common下創(chuàng)建spring_security模塊
2师抄、在spring_security引入相關(guān)依賴
? ? <dependencies>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>com.atguigu</groupId>
? ? ? ? ? ? <artifactId>common_utils</artifactId>
? ? ? ? ? ? <version>0.0.1-SNAPSHOT</version>
? ? ? ? </dependency>
? ? ? ? <!-- Spring Security依賴 -->
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? <artifactId>spring-boot-starter-security</artifactId>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>io.jsonwebtoken</groupId>
? ? ? ? ? ? <artifactId>jjwt</artifactId>? //生成token需要使用到j(luò)wt
? ? ? ? </dependency>
? ? </dependencies>
3漓柑、在service_acls引入spring_security依賴
????????<dependency>
? ? ? ? ? ? <groupId>com.atguigu</groupId>
? ? ? ? ? ? <artifactId>spring_security</artifactId>
? ? ? ? ? ? <version>0.0.1-SNAPSHOT</version>
? ? ? ? </dependency>
4、復(fù)制工具類到common_utils
5叨吮、創(chuàng)建spring security核心配置類
Spring Security的核心配置就是繼承WebSecurityConfigurerAdapter并注解@EnableWebSecurity的配置辆布。
這個配置指明了用戶名密碼的處理方式、請求路徑的開合茶鉴、登錄登出控制等和安全相關(guān)的配置锋玲。
package com.atguigu.serurity.config;
import com.atguigu.serurity.filter.TokenAuthenticationFilter;
import com.atguigu.serurity.filter.TokenLoginFilter;
import com.atguigu.serurity.security.DefaultPasswordEncoder;
import com.atguigu.serurity.security.TokenLogoutHandler;
import com.atguigu.serurity.security.TokenManager;
import com.atguigu.serurity.security.UnauthorizedEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* <p>
* Security配置類
* </p>
*
* @author qy
* @since 2019-11-18
*/
@Configuration? //表示配置類
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
? ? private UserDetailsService userDetailsService;
? ? private TokenManager tokenManager;
? ? private DefaultPasswordEncoder defaultPasswordEncoder;
? ? private RedisTemplate redisTemplate;
? ? @Autowired
? ? public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TokenManager tokenManager, RedisTemplate redisTemplate) {
? ? ? ? this.userDetailsService = userDetailsService;
? ? ? ? this.defaultPasswordEncoder = defaultPasswordEncoder;
? ? ? ? this.tokenManager = tokenManager;
? ? ? ? this.redisTemplate = redisTemplate;
? ? }
? ? /**
? ? * 配置設(shè)置
? ? * @param http
? ? * @throws Exception
? ? */
? ? @Override
? ? protected void configure(HttpSecurity http) throws Exception {
? ? ? ? http.exceptionHandling()
? ? ? ? ? ? ? ? .authenticationEntryPoint(new UnauthorizedEntryPoint())
? ? ? ? ? ? ? ? .and().csrf().disable()
? ? ? ? ? ? ? ? .authorizeRequests()
? ? ? ? ? ? ? ? .anyRequest().authenticated()
????????????????//設(shè)置退出請求地址,這個地址是由spring security做到的涵叮,因此這個地址理論上可以隨便寫
? ? ? ? ? ? ? ? .and().logout().logoutUrl("/admin/acl/index/logout")
? ? ? ? ? ? ? ? .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()
? ? ? ? ? ? ? ? .addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
? ? ? ? ? ? ? ? .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
? ? }
? ? /**
? ? * 密碼處理
? ? * @param auth
? ? * @throws Exception
? ? */
? ? @Override
? ? public void configure(AuthenticationManagerBuilder auth) throws Exception {
? ? ? ? auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
? ? }
? ? /**
? ? * 配置哪些請求不攔截
? ? * @param web
? ? * @throws Exception
? ? */
? ? @Override
? ? public void configure(WebSecurity web) throws Exception {
//? ? ? ? web.ignoring().antMatchers("/api/**",
//? ? ? ? ? ? ? ? "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
//? ? ? ? ? ? ? );
? ? ? ? web.ignoring().antMatchers("/*/**");
? ? }
}
5惭蹂、創(chuàng)建認證授權(quán)相關(guān)的工具類
(1)DefaultPasswordEncoder:密碼處理的方法
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
? ? public DefaultPasswordEncoder() {
? ? ? ? this(-1);
? ? }
? ? public DefaultPasswordEncoder(int strength) {}
? ? public String encode(CharSequence rawPassword) {
? ? ? ? return MD5.encrypt(rawPassword.toString());
? ? }
? ? public boolean matches(CharSequence rawPassword, String encodedPassword) {
? ? ? ? return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
? ? }
}
(2)TokenManager:token操作的工具類
@Component
public class TokenManager {
? ? private long tokenExpiration = 24*60*60*1000;
? ? private String tokenSignKey = "123456";
? ? public String createToken(String username) {
? ? ? ? String token = Jwts.builder().setSubject(username)
? ? ? ? ? ? ? ? .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
? ? ? ? ? ? ? ? .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
? ? ? ? return token;
? ? }
? ? public String getUserFromToken(String token) {
? ? ? ? String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
? ? ? ? return user;
? ? }
? ? public void removeToken(String token) {
? ? ? ? //jwttoken無需刪除,客戶端扔掉即可割粮。
? ? }
}
(3)TokenLogoutHandler:退出實現(xiàn)
public class TokenLogoutHandler implements LogoutHandler {
? ? private TokenManager tokenManager;
? ? private RedisTemplate redisTemplate;
? ? public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
? ? ? ? this.tokenManager = tokenManager;
? ? ? ? this.redisTemplate = redisTemplate;
? ? }
? ? @Override
? ? public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
? ? ? ? String token = request.getHeader("token");
? ? ? ? if (token != null) {
? ? ? ? ? ? tokenManager.removeToken(token);
? ? ? ? ? ? //清空當(dāng)前用戶緩存中的權(quán)限數(shù)據(jù)
? ? ? ? ? ? String userName = tokenManager.getUserFromToken(token);
? ? ? ? ? ? redisTemplate.delete(userName);
? ? ? ? }
? ? ? ? ResponseUtil.out(response, R.ok());
? ? }
}
(4)UnauthorizedEntryPoint:未授權(quán)統(tǒng)一處理
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
? ? @Override
? ? public void commence(HttpServletRequest request, HttpServletResponse response,
? ? ? ? ? ? ? ? ? ? ? ? AuthenticationException authException) throws IOException, ServletException {
? ? ? ? ResponseUtil.out(response, R.error());
? ? }
}
6盾碗、創(chuàng)建認證授權(quán)實體類
(1)SecutityUser
package com.atguigu.serurity.entity;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* <p>
* 安全認證用戶詳情信息
* </p>
*
* @author qy
* @since 2019-11-08
*/
@Data
@Slf4j
public class SecurityUser implements UserDetails {
? ? //當(dāng)前登錄用戶
? ? private transient User currentUserInfo;
? ? //當(dāng)前權(quán)限
? ? private List<String> permissionValueList;
? ? public SecurityUser() {
? ? }
? ? public SecurityUser(User user) {
? ? ? ? if (user != null) {
? ? ? ? ? ? this.currentUserInfo = user;
? ? ? ? }
? ? }
? ? @Override
? ? public Collection<? extends GrantedAuthority> getAuthorities() {
? ? ? ? Collection<GrantedAuthority> authorities = new ArrayList<>();
? ? ? ? for(String permissionValue : permissionValueList) {
? ? ? ? ? ? if(StringUtils.isEmpty(permissionValue)) continue;
? ? ? ? ? ? SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
? ? ? ? ? ? authorities.add(authority);
? ? ? ? }
? ? ? ? return authorities;
? ? }
? ? @Override
? ? public String getPassword() {
? ? ? ? return currentUserInfo.getPassword();
? ? }
? ? @Override
? ? public String getUsername() {
? ? ? ? return currentUserInfo.getUsername();
? ? }
? ? @Override
? ? public boolean isAccountNonExpired() {
? ? ? ? return true;
? ? }
? ? @Override
? ? public boolean isAccountNonLocked() {
? ? ? ? return true;
? ? }
? ? @Override
? ? public boolean isCredentialsNonExpired() {
? ? ? ? return true;
? ? }
? ? @Override
? ? public boolean isEnabled() {
? ? ? ? return true;
? ? }
}
(2)User用戶實體類:存儲用戶信息
package com.atguigu.serurity.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* <p>
* 用戶實體類
* </p>
*
* @author qy
* @since 2019-11-08
*/
@Data
@ApiModel(description = "用戶實體類")
public class User implements Serializable {
????private static final long serialVersionUID = 1L;
????@ApiModelProperty(value = "微信openid")
????private String username;
????@ApiModelProperty(value = "密碼")
????private String password;
????@ApiModelProperty(value = "昵稱")
????private String nickName;
????@ApiModelProperty(value = "用戶頭像")
????private String salt;
????@ApiModelProperty(value = "用戶簽名")
????private String token;
}
7、創(chuàng)建認證和授權(quán)的filter
(1)TokenLoginFilter:認證的filter
package com.atguigu.serurity.filter;
import com.atguigu.commonutils.R;
import com.atguigu.commonutils.ResponseUtil;
import com.atguigu.serurity.entity.SecurityUser;
import com.atguigu.serurity.entity.User;
import com.atguigu.serurity.security.TokenManager;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
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.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
/**
* <p>
* 登錄過濾器舀瓢,繼承UsernamePasswordAuthenticationFilter廷雅,對用戶名密碼進行登錄校驗
* </p>
*
* @author qy
* @since 2019-11-08
*/
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
? ? private AuthenticationManager authenticationManager;
? ? private TokenManager tokenManager;
? ? private RedisTemplate redisTemplate;
? ? public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
? ? ? ? this.authenticationManager = authenticationManager;
? ? ? ? this.tokenManager = tokenManager;
? ? ? ? this.redisTemplate = redisTemplate;
? ? ? ? this.setPostOnly(false);
????????//設(shè)置登錄請求地址,這個地址是由spring security做到的京髓,因此這個地址理論上可以隨便寫
? ? ? ? this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
? ? }
? ? //得到輸入的用戶名和密碼
? ? @Override
? ? public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
? ? ? ? ? ? throws AuthenticationException {
? ? ? ? try {
? ? ? ? ? ? User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
? ? ? ? ? ? return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
? ? ? ? } catch (IOException e) {
? ? ? ? ? ? throw new RuntimeException(e);
? ? ? ? }
? ? }
? ? /**
? ? * 登錄成功
? ? * @param req
? ? * @param res
? ? * @param chain
? ? * @param auth
? ? * @throws IOException
? ? * @throws ServletException
? ? */
? ? @Override
? ? protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Authentication auth) throws IOException, ServletException {
? ? ? ? SecurityUser user = (SecurityUser) auth.getPrincipal();
? ? ? ? String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
? ? ? ? redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
? ? ? ? ResponseUtil.out(res, R.ok().data("token", token));
? ? }
? ? /**
? ? * 登錄失敗
? ? * @param request
? ? * @param response
? ? * @param e
? ? * @throws IOException
? ? * @throws ServletException
? ? */
? ? @Override
? ? protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AuthenticationException e) throws IOException, ServletException {
? ? ? ? ResponseUtil.out(response, R.error());
? ? }
}
(2)TokenAuthenticationFilter:
授權(quán)filter
package com.atguigu.serurity.filter;
import com.atguigu.commonutils.R;
import com.atguigu.commonutils.ResponseUtil;
import com.atguigu.serurity.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* <p>
* 訪問過濾器
* </p>
*
* @author qy
* @since 2019-11-08
*/
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
? ? private TokenManager tokenManager;
? ? private RedisTemplate redisTemplate;
? ? public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
? ? ? ? super(authManager);
? ? ? ? this.tokenManager = tokenManager;
? ? ? ? this.redisTemplate = redisTemplate;
? ? }
? ? @Override
? ? protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
? ? ? ? ? ? throws IOException, ServletException {
? ? ? ? logger.info("================="+req.getRequestURI());
? ? ? ? if(req.getRequestURI().indexOf("admin") == -1) {
? ? ? ? ? ? chain.doFilter(req, res);
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? UsernamePasswordAuthenticationToken authentication = null;
? ? ? ? try {
? ? ? ? ? ? authentication = getAuthentication(req);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ResponseUtil.out(res, R.error());
? ? ? ? }
? ? ? ? if (authentication != null) {
? ? ? ? ? ? SecurityContextHolder.getContext().setAuthentication(authentication);
? ? ? ? } else {
? ? ? ? ? ? ResponseUtil.out(res, R.error());
? ? ? ? }
? ? ? ? chain.doFilter(req, res);
? ? }
? ? private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
? ? ? ? // token置于header里
? ? ? ? String token = request.getHeader("token");
? ? ? ? if (token != null && !"".equals(token.trim())) {
? ? ? ? ? ? String userName = tokenManager.getUserFromToken(token);
? ? ? ? ? ? List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
? ? ? ? ? ? Collection<GrantedAuthority> authorities = new ArrayList<>();
? ? ? ? ? ? for(String permissionValue : permissionValueList) {
? ? ? ? ? ? ? ? if(StringUtils.isEmpty(permissionValue)) continue;
? ? ? ? ? ? ? ? SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
? ? ? ? ? ? ? ? authorities.add(authority);
? ? ? ? ? ? }
? ? ? ? ? ? if (!StringUtils.isEmpty(userName)) {
? ? ? ? ? ? ? ? return new UsernamePasswordAuthenticationToken(userName, token, authorities);
? ? ? ? ? ? }
? ? ? ? ? ? return null;
? ? ? ? }
? ? ? ? return null;
? ? }
}
02-創(chuàng)建查詢用戶類和前端對接
一航缀、創(chuàng)建自定義查詢用戶類
(1)在service_acls模塊創(chuàng)建,因為其他模板不會用到堰怨。
查詢登錄和用戶權(quán)限類:UserDetailsServiceImpl ?implements UserDetailsService
二芥玉、后端接口和前端頁面對接
1、在前端項目中下載依賴
npm install --save vuex-persistedstate
2备图、替換相關(guān)文件
3飞傀、在node_modules文件夾中替換element-ui依賴
(1)修改router文件夾里面index. js 里面路徑和vue文件地址皇型。
(2)修改數(shù)據(jù)庫菜單表路徑和頁面地址。
(3)修改前端項目請求地址是網(wǎng)關(guān)地址砸烦。
測試:
輸入用戶名和密碼: