springboot集成shiro和前后端分离配置
一,springboot集成shiro
1,导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
2,Realm
shiro以来这个来进行认证和授权
package com.chen.admin.Realm;
import com.alibaba.fastjson.JSON;
import com.chen.admin.dto.UserDto;
import com.chen.admin.entity.Role;
import com.chen.admin.entity.User;
import com.chen.admin.service.RoleService;
import com.chen.admin.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @Author @Chenxc
* @Date 24-7-2 10:13
*/
@Component
public class UserPasswordRealm extends AuthorizingRealm {
@Autowired
private RoleService roleService;
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder encoder;
// @Autowired
//private StringRedisTemplate stringRedisTemplate;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
UserDto user = (UserDto) principalCollection.getPrimaryPrincipal();
//使用redis存放用户角色和权限
// Object o = stringRedisTemplate.opsForValue().get("user:" + user.getId());
// if(null == o){
// List<Role> roleList = roleService.getRoleByUserId(user.getId());
// if(null != roleList){
// Set<String> roles = new HashSet<>();
// Set<String> permissions = new HashSet<>();
// for (Role role : roleList) {
// roles.add(role.getName());
// List<String> permission = roleService.getPermissionByRoleId(role.getId());
// if(null != permission){
// permissions.addAll(permission);
// }
// }
// user.setRoleList(roles);
// user.setPermissions(permissions);
// }
// stringRedisTemplate.opsForValue().set("user:"+user.getId(), JSON.toJSONString(user));
// }else{
// String json = (String)o;
// user = JSON.parseObject(json,UserDto.class);
// }
// SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// simpleAuthorizationInfo.addRoles(user.getRoleList());
// simpleAuthorizationInfo.addStringPermissions(user.getPermissions());
//本地存放用户角色和权限
List<Role> roleList = roleService.getRoleByUserId(user.getId());
if(null != roleList){
Set<String> roles = new HashSet<>();
Set<String> permissions = new HashSet<>();
for (Role role : roleList) {
roles.add(role.getName());
List<String> permission = roleService.getPermissionByRoleId(role.getId());
if (null != permission) {
permissions.addAll(permission);
}
}
user.setRoleList(roles);
user.setPermissions(permissions);
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(user.getRoleList());
simpleAuthorizationInfo.addStringPermissions(user.getPermissions());
return simpleAuthorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
char[] password1 = token.getPassword();
if(null == username || null == password1){
throw new AuthenticationException("用户名或密码错误");
}
String password = new String(password1);
User user = userService.getUserByUsername(username);
if(null == user){
throw new AuthenticationException("用户名或密码错误");
}
boolean matches = encoder.matches(password, user.getPassword());
if (!matches) {
throw new AuthenticationException("用户名或密码错误");
}
if(user.getEnable().equals("0")){
throw new DisabledAccountException("用户已禁用");
}
UserDto dto = new UserDto();
BeanUtils.copyProperties(user,dto);
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(dto, password, getName());
return authenticationInfo;
}
}
3,shiro配置
package com.chen.admin.config;
import com.chen.admin.dao.RedisSessionDao;
import com.chen.admin.filter.ShiroFormAuthenticationFilter;
import com.chen.admin.system.Constant;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Author @Chenxc
* @Date 24-7-2 10:10
*/
@Configuration
public class ShiroConfig {
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 设置为true则会在代理对象的方法执行过程中进行权限校验
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
//上面的两个开启注解的支持,如果没有这两个方法,doGetAuthorizationInfo不会执行,加了@RequiresPermissions注解后会包找不到url
//session保存在内存
@Bean
public DefaultWebSessionManager defaultWebSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(Constant.expireTime * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
//修改Cookie中的SessionId的key,默认为JSESSIONID,自定义名称
sessionManager.setSessionIdCookie(new SimpleCookie(Constant.SHIRO_COOKIE_ID));
return sessionManager;
}
//session保存在redis
@Bean
public DefaultWebSessionManager defaultWebSessionManager(RedisSessionDao redisSessionDao){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(Constant.expireTime * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionDAO(redisSessionDao);
sessionManager.setSessionValidationSchedulerEnabled(true);
//修改Cookie中的SessionId的key,默认为JSESSIONID,自定义名称
sessionManager.setSessionIdCookie(new SimpleCookie(Constant.SHIRO_COOKIE_ID));
return sessionManager;
}
//上面的session存放选一个即可
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(DefaultWebSessionManager defaultWebSessionManager,Realm realm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 取消Cookie中的RememberMe参数
manager.setRememberMeManager(null);
manager.setRealm(realm);
manager.setSessionManager(defaultWebSessionManager);
return manager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterChainDefinition(DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(defaultWebSecurityManager);
factoryBean.setLoginUrl("/login");
//factoryBean.setSuccessUrl("/loginSuccess");
Map<String, String> map = new LinkedHashMap<>();
//map.put("/login","anon");
map.put("/static/**","anon");
map.put("/ws/**","authc");
map.put("/**", "authc");
// 配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
// shiroFilterFactoryBean.setLoginUrl("/login/unauth");
LinkedHashMap<String, Filter> filtsMap = new LinkedHashMap<>();
// 这里使用自定义的filter
filtsMap.put("authc", new ShiroFormAuthenticationFilter());
factoryBean.setFilters(filtsMap);
factoryBean.setFilterChainDefinitionMap(map);
return factoryBean;
}
}
如果上面使用redis还需要集成 AbstractSessionDAO 来读取redis中的数据来认证和授权
RedisSessionDao:
package com.chen.admin.dao;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chen.admin.system.Constant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
/**shiro集成redis
* @author @Chenxc
* @date 2024/7/3 0:46
**/
@Component
public class RedisSessionDao extends AbstractSessionDAO {
@Autowired
private RedisTemplate redisTemplate;
@Override
protected Serializable doCreate(Session session) {
Serializable serializable = this.generateSessionId(session);
this.assignSessionId(session,serializable);
redisTemplate.opsForValue().set(session.getId(),session, Constant.expireTime, TimeUnit.SECONDS);
return serializable;
}
@Override
protected Session doReadSession(Serializable serializable) {
if(serializable == null){
return null;
}
SimpleSession o = (SimpleSession)redisTemplate.opsForValue().get(serializable);
if(o == null){
return null;
}
return o;
}
@Override
public void update(Session session) throws UnknownSessionException {
if(session != null && session.getId() != null){
session.setTimeout( Constant.expireTime * 1000);
redisTemplate.opsForValue().set(session.getId(),session, Constant.expireTime,TimeUnit.SECONDS);
}
}
@Override
public void delete(Session session) {
if (session != null && session.getId() != null) {
redisTemplate.opsForValue().getOperations().delete(session.getId());
}
}
@Override
public Collection<Session> getActiveSessions() {
return redisTemplate.keys("*");
}
}
二,前后端分离配置
如果是前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据,可以继承FormAuthenticationFilter重写里面的两个方法即可:
org.apache.shiro.web.filter.authc.FormAuthenticationFilter#onAccessDenied
org.apache.shiro.web.filter.authc.FormAuthenticationFilter#onLoginSuccess
package com.chen.admin.filter;
import com.alibaba.fastjson.JSON;
import com.chen.admin.system.Result;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* @author @Chenxc
* @date 2024/7/8 1:29
**/
public class ShiroFormAuthenticationFilter extends FormAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(ShiroFormAuthenticationFilter.class);
/** 未登录时返回json
* @auther: @Chenxc //作者
* @Description //TODO //描述
* @param: //参数
* @return: //返回值
* @date: //创建日期
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (this.isLoginRequest(request, response)) {
if (this.isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return this.executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
return true;
}
} else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
}
//this.saveRequestAndRedirectToLogin(request, response);
response.setContentType("application/json; charset=utf-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println(JSON.toJSONString(Result.unauthenticated()));
out.flush();
out.close();
return false;
}
}
/** 登录成功后返回json
* @auther: @Chenxc //作者
* @Description //TODO //描述
* @param: //参数
* @return: //返回值
* @date: //创建日期
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
}
//this.saveRequestAndRedirectToLogin(request, response);
response.setContentType("application/json; charset=utf-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
Result result = Result.success("登录成功");
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String header = httpServletResponse.getHeader("Set-Cookie");
if(null != header){
String[] split = header.split(";");
if(null != split && split.length != 0){
String tokenStr = split[0].split("=")[1];
result.setData(tokenStr);
}
}
out.println(JSON.toJSONString(result));
out.flush();
out.close();
return false;
}
}
最后controller
package com.chen.admin.controller;
import com.chen.admin.entity.Menu;
import com.chen.admin.service.MenuService;
import com.chen.admin.system.Result;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author @Chenxc
* @date 2024/7/15 23:46
**/
@RestController
@RequestMapping("/menu")
@RequiresPermissions({"MENU"})//访问该controller需要的权限
public class MenuController {
@Autowired
private MenuService menuService;
@RequestMapping("/list")
public Result<Menu> list(){
return menuService.menuList();
}
@RequestMapping("/save")
public Result<Menu> save(Menu menu){
return menuService.saveMenu(menu);
}
@RequestMapping("/delete")
public Result delete(Long id){
return menuService.delete(id);
}
@RequestMapping("/update")
public Result update(Menu menu){
return menuService.updateMenu(menu);
}
}