前端发起请求携带用户名密码作为参数传到后端,接受之后使用shiro进行登录认证,并且拿到sessionId,返回给前端,前端得到sessionId后存储在前端的sessionStorage中,设置过滤器,每次发送请求时,请求头都携带sessionStorage中的sessionId,后端再次接收到请求时shiro先进行过滤,如果是跨域请求直接放行,后端配置sessionManager拿到前端发送过来的sessionId去找登录时的session对象,进行匹配,匹配成功拿到登录凭证。
pom.xml中导入依赖
<dependency>
<groupId>cn.xh.crm</groupId>
<artifactId>crm-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 引入shiro的jar包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="CrmSessionManager" class="cn.xh.crm.sessionmanager.CrmSessionManager"></bean>
<!--shiro的核心对象 realm-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--配置realm的对象-->
<property name="realm" ref="authRealm"/>
<!--配置自定义Session管理器-->
<property name="sessionManager" ref="CrmSessionManager"/>
</bean>
<!--自定义的Realm-->
<bean id="authRealm" class="cn.xh.crm.realm.MyRealm">
<!--密码匹配器-->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密方式-->
<property name="hashAlgorithmName" value="MD5"/>
<!--加密次数-->
<property name="hashIterations" value="10"/>
</bean>
</property>
</bean>
<!--自定义的认证过滤器-->
<bean id="MyCrmAuthentication" class="cn.xh.crm.filter.MyAuthenticationFilter"></bean>
<!--shiro的过滤器配置-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--认证不通过跳转的url-->
<property name="loginUrl" value="http://localhost:8080/#/login"/>
<!--认证通过跳转的url-->
<property name="successUrl" value="http://localhost:8080/#/"/>
<property name="filters">
<map>
<!--配置自定义认证过滤器-->
<entry key="myAuthc" value-ref="MyCrmAuthentication"></entry>
</map>
</property>
<!--权限map给到过滤器-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
</bean>
<!--通过动态获取授权类调用它的createFilterChainDefinitionMap方法 获取权限map-->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder"
factory-method="createFilterChainDefinitionMap"/>
<!--引入动态获取授权类-->
<bean id="filterChainDefinitionMapBuilder" class="cn.xh.crm.realm.FilterChainDefinitionMapBuilder"/>
</beans>
public class FilterChainDefinitionMapBuilder {
//权限
@Autowired
private IPermissionService permissionService;
public Map<String,String> createFilterChainDefinitionMap(){
Map<String,String> map = new LinkedHashMap<>();
map.put("/login","anon");
//查询数据库中权限对应的资源路径
List<Permission> permissions = permissionService.selectAll();
//遍历,添加
for (Permission permission : permissions) {
map.put(permission.getUrl(),"perms["+permission.getSn()+"]");
}
map.put("/**","myAuthc");
return map;
}
}
在application-shiro.xml中配置实例工厂配置的过滤器链
<!--shiro的过滤器配置-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--认证不通过跳转的url-->
<property name="loginUrl" value="http://localhost:8080/#/login"/>
<!--认证通过跳转的url-->
<property name="successUrl" value="http://localhost:8080/#/"/>
<property name="filters">
<map>
<!--配置自定义认证过滤器-->
<entry key="myAuthc" value-ref="MyCrmAuthentication"></entry>
<!--配置自定义权限过滤器-->
<!--<entry key="MyPerm" value-ref="MyPermissionsAuthorizationFilter"></entry>-->
</map>
</property>
<!--权限map给到过滤器-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
</bean>
<!--通过动态获取授权类调用它的createFilterChainDefinitionMap方法 获取权限map-->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder"
factory-method="createFilterChainDefinitionMap"/>
<!--引入动态获取授权类-->
<bean id="filterChainDefinitionMapBuilder" class="cn.xh.crm.realm.FilterChainDefinitionMapBuilder"/>
@Controller
@CrossOrigin
public class LoginController{
@Autowired
private IEmployeeService service;
@RequestMapping(value = "/login",method = RequestMethod.POST)
@ResponseBody
public JSONResult login(@RequestBody Employee employee){
Subject subject = null;
try {
//获取主体对象
subject = SecurityUtils.getSubject();
//获取前端传入的用户名,密码
UsernamePasswordToken token = new UsernamePasswordToken(employee.getUserName(),employee.getPassWord());
subject.login(token);
}catch (UnknownAccountException e){
e.printStackTrace();
return JSONResult.error("用户名或密码错误!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
return JSONResult.error("用户名或密码错误!");
}catch (AuthenticationException e){
e.printStackTrace();
return JSONResult.error("系统异常!");
}
employee.setPassWord("");
Map<String,Object> map = new HashMap<>();
map.put("user",employee);
//将sessionId传给前端
map.put("sessionId",subject.getSession().getId());
return JSONResult.ok(map);
}
}
public class MyRealm extends AuthorizingRealm {
@Autowired
private IEmployeeService employeeService;
@Autowired
private IPermissionService permissionService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前登录对象
Employee employee =(Employee) principalCollection.getPrimaryPrincipal();
//查询该用户拥有的权限
Set<String> permissionssns =
permissionService.findPermissionssnsByLoginUser(employee.getId());
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//授权
authorizationInfo.setStringPermissions(permissionssns);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取token
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
//获取用户名
String username = token.getUsername();
//查询数据库中是否有该用户
Employee employee = employeeService.selectByUsername(username);
if (employee != null){
return new SimpleAuthenticationInfo(employee,employee.getPassWord(), ByteSource.Util.bytes(employee.getSalt()),"MyRealm");
}
return null;
}
}
在application-shiro.xml中配置自定义的Realm
<!--自定义的Realm-->
<bean id="authRealm" class="cn.xh.crm.realm.MyRealm">
<!--密码匹配器-->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密方式-->
<property name="hashAlgorithmName" value="MD5"/>
<!--加密次数-->
<property name="hashIterations" value="10"/>
</bean>
</property>
</bean>
this.$http.post("/login",loginParams).then(res=>{
if(res.data.success){
sessionStorage.setItem('user', JSON.stringify(res.data.data.user));
//存储sessionId
sessionStorage.setItem('sessionId',res.data.data.sessionId);
this.$router.push({ path: '/echarts' });
this.logining = false;
}else {
this.$message.error(res.data.message);
this.logining = false;
}
}).catch(error => {
console.log(error);
this.$message.error(error);
this.logining = false;
})
//axios的拦截器 每次发送ajax请求时会经过该拦截器
axios.interceptors.request.use(config => {
/*判断用户是否已登录*/
if (sessionStorage.getItem('sessionId')) {
/*把jsessionId 命名为X-TOKEN 返回给后台,告诉shiro已经登录过*/
config.headers['X-TOKEN'] = sessionStorage.getItem('sessionId')
}
console.debug("config",config);
return config
}, error => {
// Do something with request error
Promise.reject(error)
})
public class MyAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest servletRequest = (HttpServletRequest)request;
String method = servletRequest.getMethod();
//如果是跨域请求直接放行
if ("OPTIONS".equals(method)){
return true;
}
//如果不是跨域请求,执行父类方法
return super.isAccessAllowed(request, response, mappedValue);
}
}
在application-shiro.xml中引用自定义的过滤器
<!--shiro的过滤器配置-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--认证不通过跳转的url-->
<property name="loginUrl" value="http://localhost:8080/#/login"/>
<!--认证通过跳转的url-->
<property name="successUrl" value="http://localhost:8080/#/"/>
<property name="filters">
<map>
<!--配置自定义认证过滤器-->
<entry key="myAuthc" value-ref="MyCrmAuthentication"></entry>
<!--配置自定义权限过滤器-->
<!--<entry key="MyPerm" value-ref="MyPermissionsAuthorizationFilter"></entry>-->
</map>
</property>
<!--权限map给到过滤器-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
</bean>
public class CrmSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "X-Token";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public CrmSessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//取到jessionid
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
HttpServletRequest request1 = (HttpServletRequest) request;
//如果请求头中有 X-TOKEN 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
在application-shiro.xml中配置bean,在shiro核心对象中引用
<bean id="CrmSessionManager" class="cn.xh.crm.sessionmanager.CrmSessionManager"></bean>
<!--shiro的核心对象 realm-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--配置realm的对象-->
<property name="realm" ref="authRealm"/>
<!--配置自定义Session管理器-->
<property name="sessionManager" ref="CrmSessionManager"/>
</bean>