spring boot中常用的安全框架
Security 和 Shiro 框架
Security 兩大核心功能 認證 和 授權(quán)
重量級
Shiro 輕量級框架 不限于web 開發(fā)
在不使用安全框架的時候
一般我們利用過濾器和 aop自己實現(xiàn) 權(quán)限驗證 用戶登錄
Security 實現(xiàn)邏輯
- 輸入用戶名和密碼 提交
- 把提交用戶名和密碼封裝對象
3、4 調(diào)用方法實現(xiàn)驗證
5、調(diào)用方法、根據(jù)用戶米查詢用戶信息
6、查詢用戶信息返回對象
7、密碼比較
8、填充回、返回
9、返回對象放到上下文對象里面
引入依賴
<!-- Spring Security依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
剛開始測試的話 默認密碼在控制臺
把Security框架 使用到自己項目中
具體核心組件
- 第一步、登錄接口 判斷用戶名和密碼
自定義以下組件
1、 創(chuàng)建自己相對應(yīng)的User 實體類 繼承 org.springframework.security.core.userdetails.User
在里面定義自己的實體類字段和實現(xiàn)一個CustomUser()方法
package com.oa.security.custom;
import com.oa.model.system.SysUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
public class CustomUser extends User {
/**
* 我們自己的用戶實體對象,要調(diào)取用戶信息時直接獲取這個實體對象。(這里我就不寫get/set方法了)
*/
private SysUser sysUser;
public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
super(sysUser.getUsername(), sysUser.getPassword(), authorities);
this.sysUser = sysUser;
}
public SysUser getSysUser() {
return sysUser;
}
public void setSysUser(SysUser sysUser) {
this.sysUser = sysUser;
}
}
2、 重寫 loadUserByUsername 方法
自定義一個 UserDetailsService 接口
繼承org.springframework.security.core.userdetails.UserDetailsService 下的這個類
重寫 UserDetailsService里的 loadUserByUsername方法
自定義一個 UserDetailsService 接口 的具體實現(xiàn)類 就是去數(shù)據(jù)庫驗證的實現(xiàn)類 比如 UserDetailsServiceImpl
在這個類 實現(xiàn) loadUserByUsername 方法
實現(xiàn)后 最后返回 第一步創(chuàng)建的自定義的User 實體類
因為自己自定義的User類繼承了UserDetails類
所以等于把數(shù)據(jù)交給了Security框架
UserDetailsService 接口
package com.oa.security.custom;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Component
public interface UserDetailsService extends org.springframework.security.core.userdetails.UserDetailsService {
/**
* 根據(jù)用戶名獲取用戶對象(獲取不到直接拋異常)
*/
@Override
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetailsServiceImpl實現(xiàn)接口
package com.oa.auth.service.impl;
import com.oa.auth.service.SysMenuService;
import com.oa.auth.service.SysUserService;
import com.oa.model.system.SysUser;
import com.oa.security.custom.CustomUser;
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.ArrayList;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysMenuService sysMenuService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根據(jù)用戶名進行查詢
SysUser sysUser = sysUserService.getUserByUserName(username);
if(null == sysUser) {
throw new UsernameNotFoundException("用戶名不存在!");
}
if(sysUser.getStatus().intValue() == 0) {
throw new RuntimeException("賬號已停用");
}
//根據(jù)userid查詢用戶操作權(quán)限數(shù)據(jù)
List<String> userPermsList = sysMenuService.findUserPermsByUserId(sysUser.getId());
//創(chuàng)建list集合,封裝最終權(quán)限數(shù)據(jù)
List<SimpleGrantedAuthority> authList = new ArrayList<>();
//查詢list集合遍歷
for (String perm : userPermsList) {
authList.add(new SimpleGrantedAuthority(perm.trim()));
}
// return null;
return new CustomUser(sysUser, authList);
}
}
3、 自定義一個秘密校驗器 CustomMd5PasswordEncoder 實現(xiàn) org.springframework.security.crypto.password.PasswordEncoder 接口
package com.oa.security.custom;
import com.oa.common.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {
public String encode(CharSequence rawPassword) {
return MD5.encrypt(rawPassword.toString());
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
}
}
4、 創(chuàng)建一個過濾器 來驗證token 比如 TokenLoginFilter
繼承 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
在方法里 定義四個方法 都是重寫父類方法
定義一個構(gòu)造方法
登錄認證方法 獲取輸入的用戶名和密碼,調(diào)用方法認證 attemptAuthentication() 進行賬號密碼認證 認證實際就是執(zhí)行了我們設(shè)置的第一步的內(nèi)容
認證成功調(diào)用方法 successfulAuthentication() 如果認證成功 這個方法里 就處理比如生成token 存入權(quán)限 等
認證失敗調(diào)用方法 unsuccessfulAuthentication() 如果認證失敗 這個方法里 就處理失敗的邏輯
package com.oa.security.filter;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.oa.common.jwt.JwtHelper;
import com.oa.common.result.ResponseUtil;
import com.oa.common.result.Result;
import com.oa.common.result.ResultCodeEnum;
import com.oa.security.custom.CustomUser;
import com.oa.vo.system.LoginVo;
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.HashMap;
import java.util.Map;
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private RedisTemplate redisTemplate;
//構(gòu)造方法
public TokenLoginFilter(AuthenticationManager authenticationManager,
RedisTemplate redisTemplate) {
this.setAuthenticationManager(authenticationManager);
this.setPostOnly(false);
//指定登錄接口及提交方式,可以指定任意路徑 安全框架會從這個接口里取相應(yīng)數(shù)據(jù)
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));
this.redisTemplate = redisTemplate;
}
//登錄認證
//獲取輸入的用戶名和密碼,調(diào)用方法認證
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response)
throws AuthenticationException {
try {
//獲取用戶信息 實際就是獲取的登錄接口的 那個實體類里的數(shù)據(jù)
LoginVo loginVo = new ObjectMapper().readValue(request.getInputStream(), LoginVo.class);
//封裝對象 然后把用戶名和密碼 傳入進去
Authentication authenticationToken =
new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
//調(diào)用方法
return this.getAuthenticationManager().authenticate(authenticationToken);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//認證成功調(diào)用方法
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication auth)
throws IOException, ServletException {
//獲取當(dāng)前用戶
CustomUser customUser = (CustomUser)auth.getPrincipal();
//生成token
String token = JwtHelper.createToken(customUser.getSysUser().getId(),
customUser.getSysUser().getUsername());
//獲取當(dāng)前用戶權(quán)限數(shù)據(jù),放到Redis里面 key:username value:權(quán)限數(shù)據(jù)
redisTemplate.opsForValue().set(customUser.getUsername(),
JSON.toJSONString(customUser.getAuthorities()));
//返回
Map<String,Object> map = new HashMap<>();
map.put("token",token);
ResponseUtil.out(response, Result.ok(map));
}
//認證失敗調(diào)用方法
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException failed)
throws IOException, ServletException {
ResponseUtil.out(response,Result.build(null, ResultCodeEnum.LOGIN_ERROR));
}
}
以上就是用戶調(diào)用登錄接口 利用Security框架 完成登錄
第二步、認證解析token組件:解決調(diào)用其他接口時 看看用戶有沒有登錄
判斷請求頭是否有token 如果有,認證完成 (通俗一點就是 判斷當(dāng)前是否登錄)
自定義一個 TokenAuthenticationFilter 繼承 org.springframework.web.filter.OncePerRequestFilter;
重寫 里面的方法 doFilterInternal()
package com.oa.security.filter;
import com.alibaba.fastjson.JSON;
import com.oa.common.jwt.JwtHelper;
import com.oa.common.result.ResponseUtil;
import com.oa.common.result.Result;
import com.oa.common.result.ResultCodeEnum;
import com.oa.security.custom.LoginUserInfoHelper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
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;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private RedisTemplate redisTemplate;
public TokenAuthenticationFilter(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
//如果是登錄接口,直接放行
if("/admin/system/index/login".equals(request.getRequestURI())) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
if(null != authentication) {
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
} else {
ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_ERROR));
}
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
//請求頭是否有token
String token = request.getHeader("token");
if(!StringUtils.isEmpty(token)) {
String username = JwtHelper.getUsername(token);
if(!StringUtils.isEmpty(username)) {
//當(dāng)前用戶信息放到ThreadLocal里面
LoginUserInfoHelper.setUserId(JwtHelper.getUserId(token));
LoginUserInfoHelper.setUsername(username);
//通過username從redis獲取權(quán)限數(shù)據(jù)
String authString = (String)redisTemplate.opsForValue().get(username);
//把redis獲取字符串權(quán)限數(shù)據(jù)轉(zhuǎn)換要求集合類型 List<SimpleGrantedAuthority>
if(!StringUtils.isEmpty(authString)) {
List<Map> maplist = JSON.parseArray(authString, Map.class);
System.out.println(maplist);
List<SimpleGrantedAuthority> authList = new ArrayList<>();
for (Map map:maplist) {
String authority = (String)map.get("authority");
authList.add(new SimpleGrantedAuthority(authority));
}
return new UsernamePasswordAuthenticationToken(username,null, authList);
} else {
return new UsernamePasswordAuthenticationToken(username,null, new ArrayList<>());
}
}
}
return null;
}
}
第三步、在配置類配置相關(guān)認證類
package com.oa.security.config;
import com.oa.security.custom.CustomMd5PasswordEncoder;
import com.oa.security.filter.TokenAuthenticationFilter;
import com.oa.security.filter.TokenLoginFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
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.builders.WebSecurity;
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.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity //@EnableWebSecurity是開啟SpringSecurity的默認行為
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private RedisTemplate redisTemplate;
//導(dǎo)入這個接口的時候 有可能會報錯找不到這個類
//我們有自己創(chuàng)建了一個 UserDetailsService 接口
// 如果導(dǎo)入我們的 也會報錯
// 解決方案就是 把我們自定義的 UserDetailsService 接口 繼承 框架下的UserDetailsService
// 就可以了
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private CustomMd5PasswordEncoder customMd5PasswordEncoder;
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 這是配置的關(guān)鍵,決定哪些接口開啟防護,哪些接口繞過防護
http
//關(guān)閉csrf跨站請求偽造
.csrf().disable()
// 開啟跨域以便前端調(diào)用接口
.cors().and()
.authorizeRequests()
// 指定某些接口不需要通過驗證即可訪問。登陸接口肯定是不需要認證的
//.antMatchers("/admin/system/index/login").permitAll()
// 這里意思是其它所有接口需要認證才能訪問
.anyRequest().authenticated()
.and()
//TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,這樣做就是為了除了登錄的時候去查詢數(shù)據(jù)庫外,其他時候都用token進行認證。
.addFilterBefore(new TokenAuthenticationFilter(redisTemplate),
UsernamePasswordAuthenticationFilter.class)
.addFilter(new TokenLoginFilter(authenticationManager(),redisTemplate));
//禁用session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 指定UserDetailService和加密器
auth.userDetailsService(userDetailsService).passwordEncoder(customMd5PasswordEncoder);
}
/**
* 配置哪些請求不攔截
* 排除swagger相關(guān)請求
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/admin/modeler/**","/diagram-viewer/**","/editor-app/**","/*.html",
"/admin/processImage/**",
"/admin/wechat/authorize","/admin/wechat/userInfo","/admin/wechat/bindPhone",
"/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
}
}
用戶授權(quán)
代碼在上面的類里已經(jīng)存在,這里只截圖提現(xiàn)
比如按鈕權(quán)限 哪些按鈕課余訪問
這些按鈕權(quán)限 在 數(shù)據(jù)庫存著 一般是給前端使用
但是這種操作 如果懂技術(shù)的人 可以繞過前端 直接訪問后端api接口
為了解決這個問題 所以后端也需要做用戶授權(quán)
第一步 在查詢用戶名密碼驗證的時候 把用戶的按鈕權(quán)限查詢出來
一個按鈕一般對應(yīng)的是一個接口
然后交給 Security框架
第二步驗證成功后,給前端返回token 那里從Security框架里拿出 按鈕權(quán)限
然后存到redis里
第三步用戶在請求接口的時候 這個時候把從redis里把當(dāng)前用戶的按鈕權(quán)限拿出來
然后驗證
第四步 在Security配置文件中添加上redis配置
第五步 在控制器里 controller 里添加權(quán)限注解
@PreAuthorize(“hasAuthority(‘bnt.sysRole.list’)”)
//bnt.sysRole.list 這個值 就是存在數(shù)據(jù)庫里的按鈕的字段 前端和后端可以共同使用
最后在定義以下異常處理類文章來源:http://www.zghlxwxcb.cn/news/detail-735353.html
/**
* spring security異常
* @param e
* @return
*/
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public Result error(AccessDeniedException e) throws AccessDeniedException {
return Result.fail().code(205).message("沒有操作權(quán)限");
}
這樣 驗證和 授權(quán) 就全部完成了文章來源地址http://www.zghlxwxcb.cn/news/detail-735353.html
到了這里,關(guān)于spring boot中常用的安全框架 Security框架 利用Security框架實現(xiàn)用戶登錄驗證token和用戶授權(quán)(接口權(quán)限控制)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!