代码上传到了github
后端:GitHub - chenqi13814529300/my-csdn-shiro
前端:GitHub - chenqi13814529300/my-shiro-ui
sql代码在后端代码里,这里没有用jpa,因为易上手,难精通,比较抽象,不利于未来发展
目录
数据库设计
学到这个阶段,大家Mysql应该不陌生了,既然是走Mybatis的路线,先搭建一个健壮的数据结构很重要,秉着高内聚低耦合去设计。
不可取的设计方式如下:
Id | username | password | role | permission |
我建立如下数据结构,一组是用户角色权限,一组是角色的token
用户角色权限的一组表
user
user_role
role
role_permission
permission
角色token存储的表
user_token (这些数据是生成的,你们设计个表即可)
后端项目构建
创建项目
按步骤来即可
pom依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- shiro 缓存框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
application
server:
port: 9000
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/myshiro?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
username: root
password: 123
name: defaultDataSource
建立目录
auth包下放权限过滤器,判断token等
cofig包下是shiro的配置文件
dto包下是前端传入后端的用户名和密码的实体类
handler包下是全局异常处理类
其他的你们应该知道了
实现效果
不同用户登录进去有不同的权限
登录后如下
具体实现
整体目录
entity类
User
package com.mycsdnshiro.mycsdnshiro.entity;
public class User {
private Integer userId;
private String username;
private String password;
private Integer roleId;
private String roleName;
private String permission;
private Integer permissionId;
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", username='" + username + '\'' +
", password='" + password + '\'' +
", roleId=" + roleId +
", roleName='" + roleName + '\'' +
", permission='" + permission + '\'' +
", permissionId=" + permissionId +
'}';
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public Integer getPermissionId() {
return permissionId;
}
public void setPermissionId(Integer permissionId) {
this.permissionId = permissionId;
}
public User() {
}
public User(Integer userId, String username, String password, Integer roleId, String roleName, String permission, Integer permissionId) {
this.userId = userId;
this.username = username;
this.password = password;
this.roleId = roleId;
this.roleName = roleName;
this.permission = permission;
this.permissionId = permissionId;
}
}
UserToken
package com.mycsdnshiro.mycsdnshiro.entity;
import java.time.LocalDateTime;
public class UserToken {
private Integer userId;
private String token;
// 过期时间
private LocalDateTime expireTime;
// 更新时间
private LocalDateTime updateTime;
@Override
public String toString() {
return "UserTokenMapper{" +
"userId=" + userId +
", token='" + token + '\'' +
", expireTime=" + expireTime +
", updateTime=" + updateTime +
'}';
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public LocalDateTime getExpireTime() {
return expireTime;
}
public void setExpireTime(LocalDateTime expireTime) {
this.expireTime = expireTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
public UserToken(Integer userId, String token, LocalDateTime expireTime, LocalDateTime updateTime) {
this.userId = userId;
this.token = token;
this.expireTime = expireTime;
this.updateTime = updateTime;
}
public UserToken() {
}
}
RoleAndPermission
package com.mycsdnshiro.mycsdnshiro.entity;
public class RoleAndPermission {
private Integer userId;
private Integer roleId;
private String roleName;
private String permission;
public RoleAndPermission(Integer userId, Integer roleId, String roleName, String permission) {
this.userId = userId;
this.roleId = roleId;
this.roleName = roleName;
this.permission = permission;
}
@Override
public String toString() {
return "RoleAndPermission{" +
"userId=" + userId +
", roleId=" + roleId +
", roleName='" + roleName + '\'' +
", permission='" + permission + '\'' +
'}';
}
public RoleAndPermission() {
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
}
dto层,用于封装前端传来的用户名和密码
package com.mycsdnshiro.mycsdnshiro.dto;
public class LoginDTO {
private String username;
private String password;
public LoginDTO() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public LoginDTO(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "LoginDTO{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
Mapper.xml层跟数据库相关
UserMapper
这些代码都没什么必要说的,来看此文章你们应该会增删改查了吧
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mycsdnshiro.mycsdnshiro.mapper.UserMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mycsdnshiro.mycsdnshiro.entity.User">
<id column="user_id" property="userId"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="role_id" property="roleId"/>
</resultMap>
<resultMap id="rolePermission" type="com.mycsdnshiro.mycsdnshiro.entity.RoleAndPermission">
<id column="user_id" property="userId"/>
<result column="role_id" property="roleId"/>
<result column="password" property="password"/>
<result column="role_name" property="roleName"/>
<result column="permission" property="permission"/>
</resultMap>
<select id="findByUsername" resultMap="BaseResultMap">
select
u.user_id,u.username,u.password,ur.role_id
from user u,user_role ur
where u.user_id=ur.user_id and u.username=#{username}
</select>
<select id="rolePermissionByRoleId" resultMap="rolePermission">
select
u.user_id,ur.role_id,r.role_name,p.permission
from user u,user_role ur,role r,role_permission rp,permission p
where u.user_id=ur.user_id
and r.role_id=ur.role_id
and rp.role_id=u.user_id
and rp.permission_id=p.permission_id
and ur.role_id=#{roleId}
</select>
<select id="findByUserId" resultMap="BaseResultMap">
select
u.user_id,u.username,u.password,ur.role_id
from user u,user_role ur
where u.user_id=ur.user_id and u.user_id=#{userId}
</select>
</mapper>
UserTokenMapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mycsdnshiro.mycsdnshiro.mapper.UserTokenMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mycsdnshiro.mycsdnshiro.entity.UserToken">
<id column="user_id" property="userId"/>
<result column="token" property="token"/>
<result column="expire_time" property="expireTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="findByToken" resultMap="BaseResultMap">
select
user_id,token,expire_time,update_time
from
user_token
where token=#{token}
</select>
<select id="findByUserId" resultMap="BaseResultMap">
select
user_id,token,expire_time,update_time
from
user_token
where user_id=#{userId}
</select>
<insert id="insertUserToken">
insert into user_token
(user_id,token,update_time,expire_time)
values(#{userId},#{token},#{updateTime},#{expireTime})
</insert>
<update id="updateUserToken">
UPDATE user_token
set expire_time=#{expireTime},
token=#{token},
update_time=#{updateTime}
where user_id=#{userId};
</update>
</mapper>
dao层,也就是mapper接口层
UserMapper
package com.mycsdnshiro.mycsdnshiro.mapper;
import com.mycsdnshiro.mycsdnshiro.entity.RoleAndPermission;
import com.mycsdnshiro.mycsdnshiro.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
User findByUsername(String username);
User findByUserId(Integer userId);
List<RoleAndPermission> rolePermissionByRoleId(Integer roleId);
}
UserTokenMapper
package com.mycsdnshiro.mycsdnshiro.mapper;
import com.mycsdnshiro.mycsdnshiro.entity.UserToken;
public interface UserTokenMapper {
UserToken findByToken(String token);
UserToken findByUserId(Integer userId);
int insertUserToken(UserToken userToken);
int updateUserToken(UserToken userToken);
}
service层,业务处理层
你们将明白为什么要多这一层,而不直接controller调用mapper接口
ShiroLoginService
package com.mycsdnshiro.mycsdnshiro.service;
import com.mycsdnshiro.mycsdnshiro.entity.RoleAndPermission;
import com.mycsdnshiro.mycsdnshiro.entity.User;
import com.mycsdnshiro.mycsdnshiro.entity.UserToken;
import java.util.List;
import java.util.Map;
public interface ShiroLoginService {
List<User> queryAllUser();
User findByUsername(String username);
Map<String,Object> createToken(Integer userId);
void logout(String token);
UserToken findByToken(String accessToken);
User findByUserId(Integer userId);
List<RoleAndPermission> rolePermissionByRoleId(Integer roleId);
}
ShiroLoginServiceImpl
package com.mycsdnshiro.mycsdnshiro.service.impl;
import com.mycsdnshiro.mycsdnshiro.auth.TokenGenerator;
import com.mycsdnshiro.mycsdnshiro.entity.RoleAndPermission;
import com.mycsdnshiro.mycsdnshiro.entity.User;
import com.mycsdnshiro.mycsdnshiro.entity.UserToken;
import com.mycsdnshiro.mycsdnshiro.mapper.UserMapper;
import com.mycsdnshiro.mycsdnshiro.mapper.UserTokenMapper;
import com.mycsdnshiro.mycsdnshiro.service.ShiroLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ShiroLoginServiceImpl implements ShiroLoginService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserTokenMapper userTokenMapper;
private final static int EXPIRE=12;
@Override
public List<User> queryAllUser() {
return null;
}
@Override
public User findByUsername(String username) {
return userMapper.findByUsername(username);
}
// 给当前登录的用户生成token
@Override
public Map<String, Object> createToken(Integer userId) {
Map<String,Object> result=new HashMap<>();
// 生成一个token,代表这个用户的标识
String token = TokenGenerator.generateValue();
// 当前时间
LocalDateTime now = LocalDateTime.now();
// 过期时间
LocalDateTime expireTime = now.plusHours(EXPIRE);
// 通过Id 判断是否生成过token 若有则更新,若没有则添加
UserToken tokenEntity = userTokenMapper.findByUserId(userId);
if(tokenEntity==null){
// 新增
tokenEntity = new UserToken();
tokenEntity.setUserId(userId);
tokenEntity.setToken(token);
tokenEntity.setUpdateTime(now);
tokenEntity.setExpireTime(expireTime);
// 新增就sql添加
userTokenMapper.insertUserToken(tokenEntity);
}else {
// 更新
tokenEntity.setToken(token);
tokenEntity.setUpdateTime(now);
tokenEntity.setExpireTime(expireTime);
// 更新就sql更新
userTokenMapper.updateUserToken(tokenEntity);
}
result.put("token",token);
result.put("expire",expireTime);
return result;
}
@Override
public void logout(String token) {
}
@Override
public UserToken findByToken(String accessToken) {
return userTokenMapper.findByToken(accessToken);
}
@Override
public User findByUserId(Integer userId) {
return userMapper.findByUserId(userId);
}
@Override
public List<RoleAndPermission> rolePermissionByRoleId(Integer roleId) {
return userMapper.rolePermissionByRoleId(roleId);
}
}
contorller层
ShiroLoginController
用于用户登录的
package com.mycsdnshiro.mycsdnshiro.controller;
import com.mycsdnshiro.mycsdnshiro.dto.LoginDTO;
import com.mycsdnshiro.mycsdnshiro.entity.User;
import com.mycsdnshiro.mycsdnshiro.service.ShiroLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class ShiroLoginController {
@Autowired
private ShiroLoginService shiroLoginService;
@PostMapping("login")
public Map<String, Object> login(@RequestBody @Validated LoginDTO loginDTO, BindingResult bindingResult) {
// 把后端处理后的数据,封装在里面,返回给前端
Map<String, Object> result = new HashMap<>();
//前端返回值校验
if (bindingResult.hasErrors()) {
result.put("status", 400);
result.put("msg", bindingResult.getFieldError().getDefaultMessage());
return result;
}
// 获取登录前端传入的值
String username = loginDTO.getUsername();
String password = loginDTO.getPassword();
System.out.println(username);
// 在数据库中查找是否有此用户名
User user = shiroLoginService.findByUsername(username);
if(user==null){
result.put("status",300);
result.put("mg","账号不存在");
}else if(!user.getPassword().equals(password)){
result.put("status",400);
result.put("mg","密码错误");
}else {
// 账号和密码验证正确,生成新token或者更新token并保存到数据库中
result = shiroLoginService.createToken(user.getUserId());
result.put("status",200);
result.put("mg","检验通过,登录成功");
}
return result;
}
}
ShiroBaseController
用于用户个人权限和对应操作权限的判断
package com.mycsdnshiro.mycsdnshiro.controller;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("base")
public class ShiroBaseController {
@RequiresRoles({"admin"}) //没有的话 AuthorizationException
@GetMapping("/admin")
public Map<String, Object> admin(@RequestHeader("token")String token) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有admin角色");
return map;
}
@RequiresRoles({"teacher"}) //没有的话 AuthorizationException
@GetMapping("/teacher")
public Map<String, Object> teacher(@RequestHeader("token")String token) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有teacher角色");
return map;
}
@RequiresRoles({"student"}) //没有的话 AuthorizationException
@GetMapping("/student")
public Map<String, Object> p(@RequestHeader("token")String token) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有student角色");
return map;
}
@RequiresPermissions({"insert"}) //没有的话 AuthorizationException
@PostMapping("insert")
public Map<String, Object> save(@RequestHeader("token") String token) {
System.out.println("insert");
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有insert的权力");
return map;
}
@RequiresPermissions({"delete"}) //没有的话 AuthorizationException
@DeleteMapping("delete")
public Map<String, Object> delete(@RequestHeader("token") String token) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有delete的权力");
return map;
}
@RequiresPermissions({"update"}) //没有的话 AuthorizationException
@PutMapping("update")
public Map<String, Object> update(@RequestHeader("token") String token) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有update的权力");
return map;
}
@RequiresPermissions({"select"}) //没有的话 AuthorizationException
@GetMapping("select")
public Map<String, Object> select(@RequestHeader("token") String token) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有select的权力");
return map;
}
}
config
ShiroConfig配置文件
package com.mycsdnshiro.mycsdnshiro.config;
import com.mycsdnshiro.mycsdnshiro.auth.AuthFilter;
import com.mycsdnshiro.mycsdnshiro.auth.AuthRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public SecurityManager securityManager(AuthRealm authRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(authRealm);
securityManager.setRememberMeManager(null);
return securityManager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shifoFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//auth过滤
Map<String, javax.servlet.Filter> filters = new HashMap<>();
filters.put("auth", new AuthFilter());
shiroFilter.setFilters(filters);
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
// auno 匿名访问 auth验证
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/sys/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("/doc.html", "anon");
// 下面这个是放行这个接口(不进行权限判断),因为我们登录需要请求这个接口
filterMap.put("/user/login", "anon");
//除了以上,其他都需要权限验证
filterMap.put("/**", "auth");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
auth
用户权限判断的一些类
AuthFilter
package com.mycsdnshiro.mycsdnshiro.auth;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mycsdnshiro.mycsdnshiro.utils.HttpContextUtil;
import com.mycsdnshiro.mycsdnshiro.utils.TokenUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class AuthFilter extends AuthenticatingFilter {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 生成自定义token
*
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = TokenUtil.getRequestToken((HttpServletRequest) request);
System.out.println("后端获取前端headers或者参数处的token="+token);
return new AuthToken(token);
}
/**
* 步骤1.所有请求全部拒绝访问
*
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
return true;
}
return false;
}
/**
* 步骤2,拒绝访问的请求,会调用onAccessDenied方法,onAccessDenied方法先获取 token,再调用executeLogin方法
*
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回
String token = TokenUtil.getRequestToken((HttpServletRequest) request);
System.out.println("前端请求token="+token);
if (StringUtils.isBlank(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
httpResponse.setCharacterEncoding("UTF-8");
Map<String, Object> result = new HashMap<>();
result.put("status", 403);
result.put("msg", "请先登录");
String json = MAPPER.writeValueAsString(result);
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
/**
* token失效时候调用
*/
@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", HttpContextUtil.getOrigin());
httpResponse.setCharacterEncoding("UTF-8");
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
Map<String, Object> result = new HashMap<>();
result.put("status", 403);
result.put("msg", "登录凭证已失效,请重新登录");
String json = MAPPER.writeValueAsString(result);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
}
AuthToken
package com.mycsdnshiro.mycsdnshiro.auth;
import org.apache.shiro.authc.UsernamePasswordToken;
public class AuthToken extends UsernamePasswordToken {
private String token;
public AuthToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
TokenGenerator
生成Token,其中搭配utils工具包使用,在下文
package com.mycsdnshiro.mycsdnshiro.auth;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
public class TokenGenerator {
// token生成器
public static String generateValue() {
return generateValue(UUID.randomUUID().toString());
}
private static final char[] hexCode = "0123456789abcdefgh".toCharArray();
public static String toHexString(byte[] data){
if (data==null){
return null;
}
StringBuffer r = new StringBuffer(data.length * 2);
for (byte b: data){
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
}
// 生成Token
public static String generateValue(String param){
try {
MessageDigest algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(param.getBytes());
byte[] digest = algorithm.digest();
return toHexString(digest);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("生成Token失败");
}
}
}
AuthRealm
权限判断入口,登录的时候只会走 doGetAuthenticationInfo,权限判断的时候才会两个都走
package com.mycsdnshiro.mycsdnshiro.auth;
import com.mycsdnshiro.mycsdnshiro.entity.RoleAndPermission;
import com.mycsdnshiro.mycsdnshiro.entity.User;
import com.mycsdnshiro.mycsdnshiro.entity.UserToken;
import com.mycsdnshiro.mycsdnshiro.service.ShiroLoginService;
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.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.List;
@Component
public class AuthRealm extends AuthorizingRealm {
@Autowired
private ShiroLoginService shiroLoginService;
// 授权 获取用户角色和权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 1.从 PrincipalCollection 中获取登录用户的信息
User user = (User) principals.getPrimaryPrincipal();
List<RoleAndPermission> users = shiroLoginService.rolePermissionByRoleId(user.getRoleId());
// 2.添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//
Iterator<RoleAndPermission> it = users.iterator();
while (it.hasNext()){
RoleAndPermission u=it.next();
simpleAuthorizationInfo.addRole(u.getRoleName());
simpleAuthorizationInfo.addStringPermission(u.getPermission());
System.out.println(u.getPermission());
}
return simpleAuthorizationInfo;
}
// 认证 判断token的有效性
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取token,即前端传入的token
String accessToken=(String)token.getPrincipal();
System.out.println("此处判断token有效性"+accessToken);
// 1.根据accessToken,查询用户信息(token里面就是前端传来的用户名和密码封装后的)findByToken
UserToken tokenEntity = shiroLoginService.findByToken(accessToken);
System.out.println("得到用户实体"+tokenEntity);
// 2.token失败
if(tokenEntity==null||tokenEntity.getExpireTime().isBefore(LocalDateTime.now())){
throw new IncorrectCredentialsException("token,请重新登录");
}
// 3.调用数据库的方法,从数据库中查询 username 对应的用户记录
User user = shiroLoginService.findByUserId(tokenEntity.getUserId());
// 4.若用户不存在,则异常
if(user==null){
throw new UnknownAccountException("用户不存在");
}
// 5.根据用户的情况,来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, this.getName());
return info;
}
}
utils
工具类
TokenUtil
获取前端请求体中的token
package com.mycsdnshiro.mycsdnshiro.utils;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* token工具类
*/
public class TokenUtil {
/**
* 获取请求的token
*/
public static String getRequestToken(HttpServletRequest httpRequest) {
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if (StringUtils.isBlank(token)) {
token = httpRequest.getParameter("token");
}
return token;
}
}
HttpContextUtil
package com.mycsdnshiro.mycsdnshiro.utils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class HttpContextUtil {
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
public static String getDomain(){
HttpServletRequest request = getHttpServletRequest();
StringBuffer url = request.getRequestURL();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
}
public static String getOrigin(){
HttpServletRequest request = getHttpServletRequest();
return request.getHeader("Origin");
}
}
hander
全局异常处理类
package com.mycsdnshiro.mycsdnshiro.handler;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(value = AuthorizationException.class)
public Map<String, String> handleException(AuthorizationException e) {
//e.printStackTrace();
Map<String, String> result = new HashMap<String, String>();
result.put("status", "400");
//获取错误中中括号的内容
String message = e.getMessage();
String msg=message.substring(message.indexOf("[")+1,message.indexOf("]"));
//判断是角色错误还是权限错误
if (message.contains("role")) {
result.put("msg", "对不起,您没有" + msg + "角色");
} else if (message.contains("permission")) {
result.put("msg", "对不起,您没有" + msg + "权限");
} else {
result.put("msg", "对不起,您的权限有误");
}
return result;
}
}
启动类
前面没写mapper在这记得扫描
package com.mycsdnshiro.mycsdnshiro;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.mycsdnshiro.mycsdnshiro.mapper")
public class MycsdnshiroApplication {
public static void main(String[] args) {
SpringApplication.run(MycsdnshiroApplication.class, args);
}
}
到此后端代码结束!!!
前端项目构建
创建项目
照着即可,大伙应该都会
至于前端页面样式啥的我随便写写就好,主要是咋用token
再npm install
npm run dev 运行
再加入less
npm install --save-dev less-loader@4.1.0
element ui
npm install --save element-ui
axios
npm install --save axios
前端具体实现
目录如下
config/index.js
跨域处理
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
// 只改这里---------------前端处理跨域就好了
proxyTable: {
'/api': {
target: 'http://127.0.0.1:9000',
pathRewrite: {
'^/api': ''
}
}
},
// --------------end
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
devtool: 'cheap-module-eval-source-map',
cacheBusting: true,
cssSourceMap: true
},
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
productionSourceMap: true,
devtool: '#source-map',
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
bundleAnalyzerReport: process.env.npm_config_report
}
}
main.js router.js token.js
main
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import token from './token/token.js'
import API from './api'
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.prototype.$API=API
Vue.prototype.getToken = token.isLoginToken
Vue.prototype.setToken = token.setToken
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
router.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'welcome',
component: ()=>import("@/views/Welcome"),
redirect:'login',
children:[
{
path:'login',
name:'login',
component:()=>import("@/views/Login")
},
{
path:'home',
name:'home',
component:()=>import("@/views/Home")
},
{
path:'adminManage',
name:'adminManage',
component:()=>import("@/views/AdminManage")
},
{
path:'studentManage',
name:'studentManage',
component:()=>import("@/views/StudentManage")
},
{
path:'teacherManage',
name:'teacherManage',
component:()=>import("@/views/TeacherManage")
},
]
}
]
})
token.js
import vue from 'vue'
import router from '../router'
import ElementUI from 'element-ui'
vue.use(ElementUI)
import { Message } from 'element-ui';
const isLoginToken = () => {
const token = localStorage.getItem("token");
if (!token) {
Message({
showClose: true,
message: "请先登录",
type: "error",
duration: "3000"
});
router.push({ path: "/login" });
}
return token;
}
const setToken = (token) => {
localStorage.setItem("token", token);
}
export default {
isLoginToken,
setToken
}
api
login.js
import axios from "axios";
const login = (loginInfo) => {
console.log(loginInfo);
return axios.post('api/user/login', {
...loginInfo
})
}
export default {
login,
}
base.js
import axios from 'axios'
const goAdmin = (token) => {
return axios.get('api/base/admin', {
headers: {
token: token
}
})
}
const goTeacher = (token) => {
return axios.get('api/base/teacher', {
headers: {
token: token
}
})
}
const goStudent = (token) => {
return axios.get('api/base/student', {
headers: {
token: token
}
})
}
const goInsert = (token) => {
return axios.post('/api/base/insert', {}, {
headers:{
token:token
}
})
}
const goUpdate = (token) => {
return axios.put('api/base/update', {}, {
headers:{
token:token
}
})
}
const goDelete = (token) => {
return axios.delete('api/base/delete', {
headers: {
token: token
}
})
}
const goSelect = (token) => {
console.log(token);
return axios.get('api/base/select', {
headers: {
token: token
}
})
}
export default {
goAdmin,
goTeacher,
goStudent,
goInsert,
goDelete,
goSelect,
goUpdate
}
index.js
import login from '@/api/login/login.js'
import base from '@/api/base/base.js'
export default{
login,
base
}
views
Welcome.vue
<template>
<div>
<router-view/>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
Login.vue
<template>
<div class="login_box">
<h2>登录</h2>
<el-form
:model="ruleForm"
status-icon
ref="ruleForm"
label-width="40px"
class="demo-ruleForm"
>
<el-form-item label="账户" prop="username">
<el-input
type="text"
v-model="ruleForm.username"
autocomplete="off"
></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
type="password"
v-model="ruleForm.password"
autocomplete="off"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="submitForm('ruleForm')"
>提交</el-button
>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
var checkUsername = (rule, value, callback) => {
if (!value) {
return callback(new Error("用户名不能为空"));
}
};
var validatePassword = (rule, value, callback) => {
if (value === "") {
callback(new Error("请输入密码"));
} else {
callback();
}
};
return {
ruleForm: {
password: "",
username: "",
},
rules: {
password: [{ validator: validatePassword, trigger: "blur" }],
username: [{ validator: checkUsername, trigger: "blur" }],
},
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$API.login.login(this.ruleForm).then((result) => {
if (result.status === 200) {
var data = result.data;
if (data.status === 200) {
this.setToken(data.token);
console.log(data.token);
this.$message({
showClose: true,
message: "登录成功",
type: "success",
duration: "600",
});
var jsonUser = JSON.stringify({
username: this.ruleForm.username,
});
sessionStorage.setItem("user", jsonUser);
this.ruleForm.username = "";
this.ruleForm.password = "";
this.$router.push("home");
} else {
this.$message({
showClose: true,
message: "登录失败,原因: " + data.msg,
type: "error",
duration: "3000",
});
}
} else {
this.$message({
showClose: true,
message: "登录失败,请联系管理员",
type: "error",
duration: "3000",
});
}
});
}
});
},
},
};
</script>
<style lang="less" scoped>
.login_box {
width: 100%;
}
h2 {
margin-left: 20px;
margin-top: 50px;
text-align: center;
}
.el-form {
width: 25%;
margin: 20px auto;
position: relative;
.el-form-item {
width: 100%;
}
}
.el-input {
width: 100%;
}
.el-button {
width: 100%;
position: absolute;
left: 50%;
transform: translateX(-50%);
}
</style>
Home.vue
权限检验页面
<template>
<div>
<button @click="goAdmin">admin进的</button>
<button @click="goTeacher">teacher进的</button>
<button @click="goStudent">student进的</button>
<br />
<button @click="A">增</button>
<button @click="B">删</button>
<button @click="C">改</button>
<button @click="D">查</button>
</div>
</template>
<script>
export default {
data() {
return {
token: this.getToken(),
headers: {
token: this.getToken(),
},
};
},
methods: {
showMsg(result) {
if (result.status === 200) {
const data = result.data;
if (data.status === 200) {
this.$message({
showClose: true,
message: data.msg,
type: "success",
duration: "600",
});
} else {
this.$message({
showClose: true,
message: data.msg,
type: "error",
duration: "3000",
});
}
} else {
this.$message({
showClose: true,
message: "操作失败,请联系管理员",
type: "error",
duration: "3000",
});
}
},
goAdmin() {
this.$API.base.goAdmin(this.token).then((res) => {
this.showMsg(res);
if (res.data.status == 200) {
this.$router.push("adminManage");
}
});
},
goTeacher() {
this.$API.base.goTeacher(this.token).then((res) => {
this.showMsg(res);
if (res.data.status == 200) {
this.$router.push("teacherManage");
}
});
},
goStudent() {
this.$API.base.goStudent(this.token).then((res) => {
this.showMsg(res);
if (res.data.status == 200) {
this.$router.push("studentManage");
}
});
},
// 增删改查
A() {
this.$API.base.goInsert(this.token).then((res) => {
this.showMsg(res);
});
},
B() {
this.$API.base.goDelete(this.token).then((res) => {
this.showMsg(res);
});
},
C() {
this.$API.base.goUpdate(this.token).then((res) => {
this.showMsg(res);
});
},
D() {
this.$API.base.goSelect(this.token).then((res) => {
this.showMsg(res);
});
},
},
};
</script>
<style>
</style>
流程思路
本文章参考:这位博主使用的是Jpa一些和其它的技术,我是用了mybatis并进行一些修改
一看就懂!Springboot +Shiro +VUE 前后端分离式权限管理系统_大誌的博客-CSDN博客_shiro前后端分离