一、前言
之前写过很多登录拦截,走过很多路,有一些路的,写的代码久远,已渐渐寻不到踪迹了,有些幸好有写下来,就留了链接了,先来个小结。
① 使用 JavaWeb 的 Filter+Session
,这是初步做网站的时候使用的思路
② 使用 JS
,即每次进入一个页面时,发送一个 Ajax 请求,后台使用 Servlet 获取有没有 Session 值,显然没有上面一个规范
③ 使用 Struts2
的拦截器,当时的配置文件有点繁琐,勉强使用了一阵子
Struts2 拦截是否登录过
④ 使用 SpringMVC
,体会到了代码什么叫优美
SpringMVC 实现权限管理之检验是否登录
⑤ 使用 SpringSecurity
,结合 SpringBoot,感受到什么叫简单
使用 SpringBoot + SpringSecurity 做登录认证
⑥ 使用 Shiro
,结合 SpringBoot ,还是同上的感受,也就是本文了
最近项目有点多,以后有空或许分享些使用 Shiro 基于角色、注解版的!
等等,中间,还尝试了很多其他的 框架方法、结合使用的方式,如 Shiro 结合 Maven、SSM 等 —— 不放过每一个灵感
二、效果
现在是前后端分离的,笔者是负责团队后台工作的,就不想写太多关于页面的东西了
①数据库
②界面效果
Ⅰ、如果还没有进行登录,访问其他的链接,就会返回登录界面 login.html
Ⅱ、没有输入用户名、密码,输入用户名、密码错误,返回一个错误提示的 JSON
给前端
Ⅲ、正确输入用户名、密码,返回一个正确提示的 JSON
给前端
三、代码
talk is cheap show me the code
1、代码结构
2、entity
package com.cun.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Table;
import javax.persistence.Id;
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue
private Integer id;
@Column(length = 100)
private String userName;
@Column(length = 100)
private String password;
public User() {
super();
// TODO Auto-generated constructor stub
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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;
}
}
3、dao
package com.cun.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.cun.entity.User;
public interface UserDao extends JpaRepository<User, Integer>{
/**
* 登录的时候,根据用户名获取用户实体
* @param userName
* @return
*/
@Query(value="select * from t_user where user_name=?1",nativeQuery=true)
public User getUserByUserName(String userName);
}
4、Controller
package com.cun.controller;
import java.util.HashMap;
import java.util.Map;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.cun.entity.User;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@EnableSwagger2
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public Map<String, Object> login(User user) {
Map<String, Object> map = new HashMap<String, Object>();
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
try {
subject.login(token);
map.put("success", true);
map.put("info", "登录成功!");
return map;
} catch (Exception e) {
e.printStackTrace();
map.put("success", false);
map.put("info", "用户名或者密码错误!");
return map;
}
}
}
5、ShiroConfig
package com.cun.config;
import java.util.LinkedHashMap;
import java.util.Map;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import com.cun.realm.MyRealm;
/**
* Shiro配置类
* @author linhongcun
*
*/
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
* Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/drawImage", "anon");
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 :这是一个坑呢,一不小心代码就不好使了;
// ① authc:所有url都必须认证通过才可以访问; ② anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myRealm());
return securityManager;
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*
* @return
*/
@Bean
public MyRealm myRealm() {
MyRealm myRealm = new MyRealm();
return myRealm;
}
/**
* Shiro生命周期处理器
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
* @return
*/
@Bean
@DependsOn({ "lifecycleBeanPostProcessor" })
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}
6、MyRealm
package com.cun.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import com.cun.dao.UserDao;
import com.cun.entity.User;
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserDao userDao;
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("token.getPrincipal:" + token.getPrincipal());
System.out.println("token.getCredentials:" + token.getCredentials());
String userName = token.getPrincipal().toString();
User user = userDao.getUserByUserName(userName);
if (user != null) {
// Object principal, Object credentials, String realmName
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());
return authcInfo;
} else {
return null;
}
}
}
7、yml
server:
port: 80 #为了以后访问项目不用写端口号
context-path: / #为了以后访问项目不用写项目名
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test
username: root
password: 123
jpa:
hibernate:
ddl-auto: update #数据库同步代码
show-sql: true #dao操作时,显示sql语句
8、shiro 的 pom 依赖
<!-- SpringBoot中使用 Shiro 做登录拦截 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
9、login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>登录页</h1>
<form action="/user/login" method="post">
<!-- 1、参数必须被命名为 userName,不能为 username,保持和实体中的属性一致 -->
用户名:<input type="text" id="userName" name="userName" placeholder="userName"></input><br/>
<!-- 2、同理密码参数必须被命名为 password -->
密码:<input type="password" id="password" name="password" placeholder="password"></input><br/>
<input type="submit" value="登录"></input>
</form>
</body>
</html>