这篇文章主要介绍如何使用Spring Boot+Shiro实现权限管理,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
一:配置pom.xml文件
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
二:ShiroConfig配置类
@Configuration
public class ShiroConfig {
@Bean("sessionManager")
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(24 * 60 * 60 * 1000);
// 开启会话验证器
sessionManager.setSessionValidationSchedulerEnabled(true);
// 删除失效的session
sessionManager.setDeleteInvalidSessions(true);
// 指定sessionId,使用默认的“JSESSIONID”
sessionManager.setSessionIdCookieEnabled(true);
return sessionManager;
}
/**
* 我们在使用shiro的时候,首先都会先初始化SecurityManager,
* 然后往SecurityManager中注入shiro的其他组件,像sessionManager、realm等。
*/
@Bean("securityManager")
public SecurityManager securityManager(OAuth3Realm oAuth3Realm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuth3Realm);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// oauth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth3", new OAuth3Filter());
shiroFilter.setFilters(filters);
// 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/app/**", "anon");
filterMap.put("/file/**", "anon");
filterMap.put("/shiro/login", "anon");
filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/captcha.jpg", "anon");
// 其它通过自定义的OAuth3Filter进行过滤
filterMap.put("/**", "oauth3");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* @Title: defaultAdvisorAutoProxyCreator
* @Description: 扫描上下文,寻找所有的Advistor(通知器),将这些Advisor应用到所有符合切入点的Bean中
* @return Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
* @return DefaultAdvisorAutoProxyCreator 返回类型
* @throws
*/
// @Bean
// public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
// DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
// proxyCreator.setProxyTargetClass(true);
// return proxyCreator;
// }
/**
* @Title: authorizationAttributeSourceAdvisor
* @Description: 开启shiro aop注解支持
* @param securityManager
* @return 参数说明
* @return AuthorizationAttributeSourceAdvisor 返回类型
* @throws
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
其中,如果启用了DefaultAdvisorAutoProxyCreator的话,会导致二次代理的问题,Realm中的doGetAuthorizationInfo会重复调用2次。
三:自定义Shiro Filter类OAuth3Filter
public class OAuth3Filter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isNullOrEmpty(token)){
return null;
}
return new OAuth3Token(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//现在vue项目中使用axios发送http请求,每次请求都会多一次Request Method: OPTIONS请求,称为“预检”请求
if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){
return true;
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isNullOrEmpty(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());
String json = new Gson().toJson(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest){
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if(StringUtils.isNullOrEmpty(token)){
token = httpRequest.getParameter("token");
}
return "112233445566";//token;
}
}
对于复杂的跨域请求,Vue会首先发送一个OPTIONS请求,进行验证。需要后端对所有接口统一处理放行OPTIONS方法(即返回200)即可
四:自定义Token
public class OAuth3Token implements AuthenticationToken {
private String token;
public OAuth3Token(String token) {
this.token = token;
}
/*
* 登录提交的用户名
*
*/
@Override
public String getPrincipal() {
return token;
}
/*
* 只被Subject 知道的秘密值,比如我们登录提供的密码
*
*/
@Override
public Object getCredentials() {
return token;
}
}
五:自定义OAuth3Realm
@Component
public class OAuth3Realm extends AuthorizingRealm {
@Autowired
private ShiroService shiroService;
/*
* 判断此Realm是否支持此Token
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth3Token;
}
/*
* 提供用户信息返回权限信息
*
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("=============获取已登录用户,权限信息============");
SysUserEntity user = (SysUserEntity) principals.getPrimaryPrincipal();
Long userId = user.getUserId();
// 用户权限列表
Set<String> permsSet = shiroService.getUserPermissions(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
/*
* 根据token获取认证信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("=============获取已登录用户,用户信息============");
String accessToken = (String) token.getPrincipal();
// 根据accessToken,查询用户信息
SysUserTokenEntity tokenEntity = shiroService.queryByToken(accessToken);
// token失效
if (tokenEntity == null) {
throw new IncorrectCredentialsException("token失效,请重新登录");
}
// 查询用户信息
SysUserEntity user = shiroService.queryUser(tokenEntity.getUserId());
// 账号锁定
if (user.getStatus().equals("00")) {
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName() // 返回一个唯一的Realm名字
);
return info;
}
}
shiroService用来从数据库或者缓存中,查询用户信息和权限信息。
六:TokenGenerator
public class TokenGenerator {
public static String generateValue() {
return generateValue(UUID.randomUUID().toString());
}
private static final char[] hexCode = "0123456789abcdef".toCharArray();
public static String toHexString(byte[] data) {
if(data == null) {
return null;
}
StringBuilder r = new StringBuilder(data.length*2);
for ( byte b : data) {
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
}
public static String generateValue(String param) {
try {
MessageDigest algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(param.getBytes());
byte[] messageDigest = algorithm.digest();
return toHexString(messageDigest);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
七:Controller层Demo
@RestController
@RequestMapping("shiro")
public class ShiroController {
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "login";
}
@RequestMapping(value = "/info", method = RequestMethod.GET)
@RequiresPermissions("sys:config:info")
public String info() {
return "info";
}
}
八:登录代码
@PostMapping("/sys/login")
public Map<String, Object> login(@RequestBody SysLoginForm form)throws IOException {
boolean captcha = captchaService.validate(form.getUuid(), form.getCaptcha());
if(!captcha){
return R.error("验证码不正确");
}
//用户信息
SysUserEntity user = sysUserService.queryByUserName(form.getUsername());
//账号不存在、密码错误
if(user == null || !user.getPassword().equals(new Sha256Hash(form.getPassword(), user.getSalt()).toHex())) {
return R.error("账号或密码不正确");
}
//账号锁定
if(Constant.CommonStatus.BANNED.getValue().equals(user.getStatus())){
return R.error("账号已被锁定,请联系管理员");
}
//生成token,并保存到数据库
R r = sysUserTokenService.createToken(user.getUserId());
return r;
}
登录验证,独立实现,验证成功后创建Token,Token放到Redis缓存中。前端页面请求接口时要带Token.
九:代码调用流程
1.登录成功后,创建Token,后面的接口调用都要传递Token
2.接口首先通过shiroFilter过滤,确定是否要进入OAuth3Filter进行处理
3.OAuth3Filter 首先执行isAccessAllowed方法,如果时OPTIONS请求直接放行,否则进行Shiro的executeLogin验证
4.executeLogin执行的时候会到OAuth3Realm中调用doGetAuthenticationInfo方法,根据Token获取当前登录用户的信息(Token与用户的关联,已在第八点自己实现的登录代码中实现)
5.executeLogin成功后,会看当前访问的接口,有无权限注解@RequiresPermissions
6.如果有注解的话,说需要进行权限验证,Shiro会通过OAuth3Realm的doGetAuthorizationInfo方法,获取当前用户的权限进行验证
以上是“如何使用Spring Boot+Shiro实现权限管理”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注亿速云行业资讯频道!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:http://blog.itpub.net/28624388/viewspace-2660254/