项目结构:
访问流程:
项目启动类:
@SpringBootApplication
@MapperScan(value = "com.fy.dao")
public class SbMyShiroApplication {
public static void main(String[] args) {
SpringApplication.run(SbMyShiroApplication.class, args);
}
}
<dependencies>
<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>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>5.1.47</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
#配置数据源
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://localhost:3306/ssm_shiro
spring.datasource.druid.password=root
spring.datasource.druid.username=root
#日志
logging.level.com.fy.dao=debug
#映射文件地址
mybatis-plus.mapper-locations=classpath*:/mapper/*.xml
在ssm项目中,shiro的组件创建在spring配置文件中,在springboot项目中,需要自己创建一个配置类
//这个注解让该类相当于spring的配置文件
@Configuration
public class ShiroConfig {
// 配置安全管理器
@Bean(value = "SecurityManager")
public DefaultWebSecurityManager getSecurityManager(MyRealm MyRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 将realm交给安全管理器管理
securityManager.setRealm(MyRealm);
return securityManager;
}
// 创建MyRealm对象
@Bean(value = "MyRealm")
public MyRealm getMyRealm(HashedCredentialsMatcher CredentialsMatcher){
MyRealm myRealm = new MyRealm();
// 给Realm配置密码匹配器
myRealm.setCredentialsMatcher(CredentialsMatcher);
return myRealm;
}
// 创建密码匹配器
@Bean(value = "CredentialsMatcher")
public HashedCredentialsMatcher getCredentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
// 配置过滤规则
@Bean(value = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactory(SecurityManager SecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置要过滤的安全管理器
shiroFilterFactoryBean.setSecurityManager(SecurityManager);
// 未登录访问的跳转路径
shiroFilterFactoryBean.setLoginUrl("/tologin");
// 登录成功时访问的路径
shiroFilterFactoryBean.setSuccessUrl("/success.html");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("/login.html","anon");
hashMap.put("/login","anon");
hashMap.put("/static/**","anon");
hashMap.put("/**","authc");
// 其他需要设置的路径
shiroFilterFactoryBean.setFilterChainDefinitionMap(hashMap);
return shiroFilterFactoryBean;
}
// 配置过滤器 相当于ssm项目中web.xml的给shiro配置过滤器
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setName("shiroFilter");
filterRegistrationBean.setFilter(new DelegatingFilterProxy());
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
//springboot项目只能使用html页面,使shiro标签库可以在thymeleaf中使用
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
springboot项目不支持jsp,只能使用html,要使用thymeleaf模板,需要导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
1、login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
账号:<input type="text" name="username"/><br>
密码:<input type="text" name="userpwd"/><br>
<input type="submit" value="登陆"/>
<input type="button" value="注册" onclick="location.href='regist'"/>
</form>
</body>
</html>
2、success.html
在这个页面中需要使用shiro的标签,要在页面中引入shiro的标签,也要在pom.xml中引入依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!DOCTYPE html>
<!--引入shiro的标签-->
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登陆成功 <h1>欢迎来到</h1>
<shiro:hasPermission name="user:query">
<a href="/user/query">查询所有用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="user:update">
<a href="/user/update">修改用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete">
<a href="/user/delete">删除用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="user:insert">
<a href="/user/insert">添加用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="user:export">
<a href="/user/export">导出用户</a><br>
</shiro:hasPermission>
</body>
</html>
3、un.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
无权访问
</body>
</html>
1、dao
①UserDao
public interface UserDao extends BaseMapper<User> {
/**
* 根据用户id查询该用户具有的权限
* @param userid
* @return
*/
List<String> findPermissionByUserid(Integer userid);
}
2、entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer userid;
private String username;
private String userpwd;
private String sex;
private String address;
private String salt; //盐
}
3、service
UserService接口
public interface UserService{
User selectOne(String username);
List<String> findPermissionByUserid(Integer userid);
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
// 使用的mybatis-plus中的方法
@Override
public User selectOne(String username) {
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.eq("username",username);
User user = userDao.selectOne(wrapper);
return user;
}
@Override
public List<String> findPermissionByUserid(Integer userid) {
return userDao.findPermissionByUserid(userid);
}
}
4、user的映射文件
UserMapper.xml
<?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.fy.dao.UserDao">
<select id="findPermissionByUserid" resultType="String">
select percode from user_role ur,role_permission rp, permission p
where ur.roleid=rp.roleid and rp.perid=p.perid and ur.userid=#{userid}
</select>
</mapper>
5、Controller层
①LoginController: 登录业务
@Controller
public class LoginController {
private UsernamePasswordToken token;
@PostMapping("/login")
public String login(String username,String userpwd){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, userpwd);
try {
subject.login(token);
return "success";
}catch (Exception e){
return "login";
}
}
}
②PageController: 未登录时跳转到login.html
@Controller
public class PageController {
@RequestMapping("tologin")
public String tologin(){
System.out.println("");
return "login";
}
}
③UserController:
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("query")
@RequiresPermissions("user:query")
public String query(){
return "user:query";
}
@GetMapping("delete")
@RequiresPermissions("user:delete")
public String delete(){
return "user:delete";
}
@GetMapping("update")
@RequiresPermissions("user:update")
public String update(){
return "user:update";
}
@GetMapping("insert")
@RequiresPermissions("user:insert")
public String insert(){
return "user:insert";
}
@GetMapping("export")
@RequiresPermissions("user:export")
public String export(){
return "user:export";
}
}
6、handler层
ExceptionController : 处理异常
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(value = AuthorizationException.class)
public String handler(){
return "un";
}
}
7、realm层
MyRealm.java
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userServiceimpl;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user =(User) principals.getPrimaryPrincipal();
List<String> permission = userServiceimpl.findPermissionByUserid(user.getUserid());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if(permission.size()>0){
info.addStringPermissions(permission);
}
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = token.getPrincipal().toString();
User user = userServiceimpl.selectOne(username);
if(user!=null){
ByteSource salt = ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getUserpwd(), salt, this.getName());
return info;
}
return null;
}
}
前后端完成分离,后端返回给访问者的是json数据
该项目中有三个地方需要改造返回json数据:
1、认证成功或认证失败时
2、出现异常时,例如权限不足时
3、未登录访问时
操作:
1、创建返回的json数据格式
utils层-----》CommonResult.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult {
private Integer code;
private String msg;
private Object data;
}
2、改造Controller
将Controller层的@Controller都改成@RestController,将@ControllerAdvice改成@RestControllerAdvice
①、LoginController
@PostMapping("login")
public CommonResult login(String username, String userpwd){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,userpwd);
try {
subject.login(token);//realm的认证功能
//getPrincipl()可以得到SimpleAuthenticationInfo中的数据
Object user = subject.getPrincipal();
return new CommonResult(2000,"登陆成功",user);
}catch (Exception e){
return new CommonResult(5000,"登陆失败",null);
}
}
②、ExceptionController 异常处理类
@RestControllerAdvice
public class MyHandlerException {
@ExceptionHandler(value = UnauthorizedException.class)
public CommonResult unauthorizedException(){
return new CommonResult(5001,"权限不足",null);
}
}
③、未登录访问时返回json有两种改造方式
第一种:改PageController
@RestController
public class PageController {
@GetMapping("/tologin")
public CommonResult loginPage(){
return new CommonResult(5002,"请先登录",null);
}
}
第二种:改ShiroConfig文件,创建一个过滤器
ShiroConfig.java中改过滤规则中的内容,加上过滤器配置
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/tologin");
shiroFilterFactoryBean.setSuccessUrl("/success.html");
Map<String,String> map=new HashMap<>();
map.put("/index.html","anon");
map.put("/static/**","anon");
map.put("/login","anon"); //anon表示放行
map.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//未登录时的过滤器
Map<String, Filter> filterMap=new HashMap<>();
filterMap.put("authc",new LoginFilter());
shiroFilterFactoryBean.setFilters(filterMap);
return shiroFilterFactoryBean;
}
在filter层创建一个过滤器:
LoginFilter.java
public class LoginFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
response.setContentType("application/json;charset=utf-8");
CommonResult commonResult=new CommonResult(5002,"未登录",null);
response.getWriter().print(JSON.toJSONString(commonResult));
return false;
}
}
近日,Microsoft 工程师为 Linux 5.12 贡献了完整性子系统更新 ,并已合并到主线...
本文将介绍一段实例代码,来讲解利用正则表达式使C#判断输入日期格式是否正确的...
Git使用教程 一:Git是什么? Git是目前世界上最先进的分布式版本控制系统。 二...
2020年5月20日 全球领先的多云应用服务厂商F5公司以线上峰会的形式开启了F5 2020...
本文实例为大家分享了JavaScript实现轮播图的具体代码,供大家参考,具体内容如...
Asp 使用 Microsoft.XMLHTTP 抓取网页内容(没用乱码),并过滤需要的内容 示例源...
上个月,CPO 杂志撰写了一篇深度文章,讨论了谷歌在保护用户免受恶意扩展侵扰方...
3月2日消息 外媒 Neowin 报道,微软博客今天公布了 Windows Terminal 的最新预览...
手机中点击网页链接实现拨号或保存电话功能实现代码 通过网页拨打电话 复制代码 ...
方法1:设置readonly属性为true。 INPUT value=readonly readOnly 方法2:设置di...