文章目录
1.测试jsp
先导入jsp解析依赖:
<!--JSP解析依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
出现上图的问题是因为IDEA与JSP兼容性不好,做个配置可以解决
2.配置shiro
引入shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.1</version>
</dependency>
编写shiro配置类:
自定义realm:
shiro过滤器说明:
3.使用固定用户名和密码登陆
编写登陆接口:
@Controller
@RequestMapping("user")
public class UserController {
/**
* 用来处理身份认证
* @param username
* @param password
* @return
*/
@RequestMapping("login")
public String login(String username, String password) {
try {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
//用户名和密码鉴权
subject.login(new UsernamePasswordToken(username, password));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误!");
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
return "redirect:/login.jsp";
}
}
添加退出功能
4.连接数据库认证(重点md5+salt)
引入mybatis-plus及Mysql相关依赖:
<!-- mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
<!--mysql-connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
建表语句:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_perms`;
CREATE TABLE `t_perms` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(80) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(60) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms` (
`id` int(6) NOT NULL,
`roleid` int(6) DEFAULT NULL,
`permsid` int(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`id` int(6) NOT NULL,
`userid` int(6) DEFAULT NULL,
`roleid` int(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
用户实体类:
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User implements Serializable {
@TableId(value = "id",type = IdType.AUTO)
private String id;
private String username;
private String password;
private String salt;
//定义角色集合
private List<Role> roles;
}
资源实体类:
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_perms")
public class Perms implements Serializable {
@TableId(value = "id",type = IdType.AUTO)
private String id;
private String name;
private String url;
}
角色实体类:
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_role")
public class Role implements Serializable {
@TableId(value = "id",type = IdType.AUTO)
private String id;
private String name;
//定义权限的集合
private List<Perms> perms;
}
用户Mapper:
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
用户Service:
public interface UserService extends IService<User> {
void register(User user);
}
用户Service实现:
@Service
public class UserSerciceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
@Transactional
public void register(User user) {
//1.生成随机盐
String salt = SaltUtils.getSalt(8);
//2.将随机盐保存到数据
user.setSalt(salt);
//3.明文密码进行md5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
user.setPassword(md5Hash.toHex());
baseMapper.insert(user);
}
}
进行登陆:
5.授权
5.1页面授权
将首页写详细些:
<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<%-- 受限资源--%>
<h1>系统主页</h1>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
<ul>
<%-- 用户管理模块需要user或admin角色--%>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理</a>
<ul>
<%-- 添加删除等操作需要的权限--%>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">订单管理</a></li>
<ul>
<shiro:hasPermission name="order:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</shiro:hasAnyRoles>
<shiro:hasRole name="admin">
<li><a href="">商品管理</a></li>
<li><a href="">物流管理</a></li>
</shiro:hasRole>
<shiro:hasRole name="user">
<li><a href="">仅普通用户可见</a></li>
<li><a href="">公共资源</a></li>
</shiro:hasRole>
</ul>
</body>
</html>
5.2 代码控制权限
以上都是前端控制页面可见性而已,接下来我们在后台授权。
先使用代码授权方式:
订单保存接口
@Controller
@RequestMapping("order")
public class OrderController {
@RequestMapping("save")
public String save(){
System.out.println("进入方法");
//基于角色
//获取主体对象
Subject subject = SecurityUtils.getSubject();
//代码方式
if (subject.hasRole("root")) {
System.out.println("保存订单!");
}else{
System.out.println("无权访问!");
}
//基于权限字符串
//....
return "redirect:/index.jsp";
}
}
5.3注解控制权限
将订单保存接口修改
@RequiresRoles(value={"admin","user"})//用来判断角色 同时具有 admin user
@RequiresPermissions("order:add:*") //用来判断权限字符串
@RequestMapping("save")
public String save(){
System.out.println("进入方法");
return "redirect:/index.jsp";
}
5.4数据控动态权限
mapper添加查询方法:
@Mapper
public interface UserMapper extends BaseMapper<User> {
//根据用户名查询所有角色
User findRolesByUserName(String username);
//根据角色id查询权限集合
List<Perms> findPermsByRoleId(String id);
}
编写xml文件(联查在xml中写语句较好):
<resultMap id="userMap" type="User">
<id column="uid" property="id"/>
<result column="username" property="username"/>
<!--角色信息-->
<collection property="roles" javaType="list" ofType="Role">
<id column="id" property="id"/>
<result column="rname" property="name"/>
</collection>
</resultMap>
<select id="findRolesByUserName" parameterType="String" resultMap="userMap">
SELECT u.id uid,u.username,r.id,r.NAME rname
FROM t_user u
LEFT JOIN t_user_role ur
ON u.id=ur.userid
LEFT JOIN t_role r
ON ur.roleid=r.id
WHERE u.username=#{username}
</select>
<select id="findPermsByRoleId" parameterType="String" resultType="Perms">
SELECT p.id,p.NAME,p.url,r.NAME
FROM t_role r
LEFT JOIN t_role_perms rp
ON r.id=rp.roleid
LEFT JOIN t_perms p ON rp.permsid=p.id
WHERE r.id=#{id}
</select>
</mapper>
service及实现类:
public interface UserService extends IService<User> {
void register(User user);
/**
* 根据用户名查询用户
* @param username
* @return
*/
User findByUserName(String username);
/**
* 根据用户名查询所有角色
* @param username
* @return
*/
User findRolesByUserName(String username);
/**
* 根据角色id查询权限集合
* @param id
* @return
*/
List<Perms> findPermsByRoleId(String id);
@Service
public class UserSerciceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
@Transactional
public void register(User user) {
//1.生成随机盐
String salt = SaltUtils.getSalt(8);
//2.将随机盐保存到数据
user.setSalt(salt);
//3.明文密码进行md5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
user.setPassword(md5Hash.toHex());
baseMapper.insert(user);
}
@Override
public User findByUserName(String username) {
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.lambda().select(User::getId,User::getPassword,User::getUsername,User::getSalt).eq(User::getUsername,username);
return baseMapper.selectOne(wrapper);
}
@Override
public User findRolesByUserName(String username) {
return baseMapper.findRolesByUserName(username);
}
@Override
public List<Perms> findPermsByRoleId(String id) {
return baseMapper.findPermsByRoleId(id);
}
}
数据库中自己添加演示数据
将授权改为数据库动态授权
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//身份信息
String principal = (String) principals.getPrimaryPrincipal();
User user = userService.findRolesByUserName(principal);
System.out.println("user:"+user);
//授权角色信息
if(!CollectionUtils.isEmpty(user.getRoles())){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role->{
simpleAuthorizationInfo.addRole(role.getName()); //添加角色信息
//权限信息
List<Perms> perms = userService.findPermsByRoleId(role.getId());
System.out.println("perms:"+perms);
if(!CollectionUtils.isEmpty(perms) && perms.get(0)!=null ){
perms.forEach(perm->{
simpleAuthorizationInfo.addStringPermission(perm.getName());
});
}
});
return simpleAuthorizationInfo;
}
return null;
}
6.实现缓存
6.1 EhCache缓存
引入EhCache依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.7.1</version>
</dependency>
开启缓存管理器:
完整代码:
@Configuration
public class ShiroConfig {
//1.创建shiroFilter
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String,String> map = new HashMap<String,String>();
map.put("/**","authc"); //所有路径需要认证授权
map.put("/user/login","anon");//登录路径不用认证和授权,anon表示允许匿名访问
map.put("/register.jsp","anon"); //注册页面无需认证
map.put("/user/register","anon"); //注册接口无需认证
//默认认证界面路径---当认证不通过时跳转
// shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//2.创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
Package aPackage = realm.getClass().getPackage();
System.out.println(aPackage.getName());
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
//3.创建自定义realm
@Bean("realm")
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//设置hashed凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置md5加密
credentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
//开启缓存管理器
customerRealm.setCachingEnabled(true);
customerRealm.setAuthorizationCachingEnabled(true);
customerRealm.setAuthorizationCachingEnabled(true);
customerRealm.setCacheManager(new EhCacheManager());
return customerRealm;
}
}
6.2 redis缓存
引入redis依赖:
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置redis:
编写自定义缓存管理器:
//自定义shiro缓存管理器
public class RedisCacheManager implements CacheManager {
//参数1:认证或者是授权缓存的统一名称
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
System.out.println(cacheName);
return new RedisCache<K,V>(cacheName);
}
}
自定义缓存管理实现类:
//自定义redis缓存的实现
public class RedisCache<k,v> implements Cache<k,v> {
private String cacheName;
public RedisCache() {
}
public RedisCache(String cacheName) {
this.cacheName = cacheName;
}
@Override
public v get(k k) throws CacheException {
System.out.println("get key:"+k);
return (v) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
}
@Override
public v put(k k, v v) throws CacheException {
System.out.println("put key: "+k);
System.out.println("put value:"+v);
getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
return null;
}
@Override
public v remove(k k) throws CacheException {
System.out.println("=============remove=============");
return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
}
@Override
public void clear() throws CacheException {
System.out.println("=============clear==============");
getRedisTemplate().delete(this.cacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
@Override
public Set<k> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<v> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
获取BEAN工具类:
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
//根据bean名字获取工厂中指定bean 对象
public static Object getBean(String beanName){
System.out.println("beanName"+beanName);
Object object=context.getBean(beanName);
System.out.println("object"+object);
return context.getBean(beanName);
}
}
7. 添加验证码
7.1 引入Hutool依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
7.2 编写获取验证码接口:
@RequestMapping("getImage")
public void getImage(HttpSession session, HttpServletResponse response) throws IOException {
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
//生成验证码
String code = lineCaptcha.getCode();
//验证码放入session
session.setAttribute("code",code);
//验证码存入图片
ServletOutputStream os = response.getOutputStream();
response.setContentType("image/png");
lineCaptcha.write(os);
//输出流自己关
os.close();
}
7.3 放行验证码并在登录页添加验证码:
<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>登录</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username" > <br/>
密 码: <input type="text" name="password"> <br>
请输入验证码: <input type="text" name="code"><img src="${pageContext.request.contextPath}/user/getImage" alt=""><br>
<input type="submit" value="登录">
</form>
</body>
</html>
7.4 登录接口添加验证码验证:
/**
* 用来处理身份认证
* @param username
* @param password
* @return
*/
@RequestMapping("login")
public String login(HttpSession session, String username,String code, String password) {
//比较验证码
String codes = (String) session.getAttribute("code");
try {
if (codes.equalsIgnoreCase(code)) {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
//用户名和密码鉴权
subject.login(new UsernamePasswordToken(username, password));
return "redirect:/index.jsp";
}else {
throw new RuntimeException("验证码错误!");
}
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误!");
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
return "redirect:/login.jsp";
}