目录
6.4 创建controller service dao entity(与ssm一致)
1.shiro的底层原理分析
1.1认证原理
(1)通过ini配置文件创建securityManager
(2)调用subject.login方法主体提交认证,提交的token
(3)securityManager进行认证,securityManager最终由ModularRealmAuthenticator进行认证。
(4)ModularRealmAuthenticator调用IniRealm(给realm传入token) 去ini配置文件中查询用户信息
(5)IniRealm根据输入的token(UsernamePasswordToken,即这里的token是用户从页面输入的信息)从 shiro-first.ini查询用户信息(这里是测试阶段,后面都是查询的数据库,注入service,调用dao),根据账号查询用户信息(账号和密码)
如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码)
如果查询不到,就给ModularRealmAuthenticator返回null
(6)ModularRealmAuthenticator接收IniRealm返回Authentication认证信息
如果返回的认证信息是null,ModularRealmAuthenticator抛出异常(org.apache.shiro.authc.UnknownAccountException)
如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在)和 token中的密码 进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)
小结:
ModularRealmAuthenticator作用进行认证,需要调用realm查询用户信息(在数据库中存在用户信息)
ModularRealmAuthenticator进行密码对比(认证过程)。
realm:需要根据token中的身份信息去查询数据库(入门程序使用ini配置文件),如果查到用户返回认证信息,如果查询不到返回null。
1.2 授权原理
(1)对subject进行授权,调用方法isPermitted("permission串")
(2)SecurityManager执行授权,通过ModularRealmAuthorizer执行授权
(3)ModularRealmAuthorizer执行realm(自定义的CustomRealm)从数据库查询权限数据
调用realm的授权方法:doGetAuthorizationInfo
(4)realm从数据库查询权限数据,返回ModularRealmAuthorizer
(5)ModularRealmAuthorizer调用PermissionResolver进行权限串比对
(6)如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。
1.3 授权方式
有三种方式:
(1) 编程式:通过写if/else 授权代码块完成:(这种比较少用,一般在项目中采用后两种)
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
(2) 注解式:通过在执行的Java方法上放置相应的注解完成:
@RequiresPermissions(value = {"user:query"},logical = Logical.OR)//使用shiro注解
public String query(){
return "user:query";
}
(3) JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
在jsp页面导入shiro的标签既可以使用shiro的标签来进行权限的判断:
Jsp页面添加:
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
1.4 shiro过滤器
过滤器简称 | 对应的java类 | 描述 |
anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 匿名过滤器 |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 如果继续操作,需要做出对应的表单验证否则不能通过 |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
基本http验证过滤,如果不通过跳转至登录页面 |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 权限过滤器 |
port | org.apache.shiro.web.filter.authz.PortFilter | 端口过滤器可以设置是否是指向端口,如果不是跳转到登录页面 |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | Http方法过滤器,可以指定如post的不能进行访问等 |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 角色过滤器判断,当前用户是否指定角色 |
ssl | org.apache.shiro.web.filter.authz.SslFilter | 请求需要通过ssl,如果不是跳转回登录页面 |
user | org.apache.shiro.web.filter.authc.UserFilter | 如果访问一个已知用户,比如记住我功能,然后走这个过滤器 |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | 登录退出过滤器 |
anon:/admins/**=anon,没有参数,表示可以匿名使用。
authc:/admins/user/**=authc,没有参数,表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,通过记住我认证通过的不可以访问;
perms:/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法;
user:/admins/user/**=user,没有参数,表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查;
logout:/logout=logout,没有参数,表示退出登录,清楚session。
2.Realm对应操作
通过分析源码 ,最终认证交于realm完成认证功能,返回一个info, 如果info不为null,则进行密码比对。如果希望用数据库中的账号和密码完成认证功能,则只需要我们自定义一个类并继承AuthenticatingRealm。
2.1 自定义Realm认证
public class MyRealm extends AuthenticatingRealm {
private UserService userService=new UserService();
//该方法用于完成认证的功能
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.根据token获取账号
String username = (String) authenticationToken.getPrincipal();
//2.根据账号查询用户信息
User user = userService.findByUsername(username);
if(user!=null){
//从数据库中获取的密码
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
return info;
}
return null;
}
}
2.2 加密
shiro它提供了很多中加密器。其中使用最多就是HashedCredentialsMatcher。它的底层使用的是Md5加密。了解一下MD5加密:
public class Test03 {
public static void main(String[] args) {
//source:需要加密的明文
Md5Hash md5Hash = new Md5Hash("123456");
System.out.println(md5Hash);//e10adc3949ba59abbe56e057f20f883e密文---默认md5加密是不可逆的。但是网上由一些破解软件
//salt:盐
Md5Hash md5Hash1=new Md5Hash("123456","abc?!");
System.out.println(md5Hash1);
//设置n次加密
Md5Hash md5Hash2 = new Md5Hash("123456","abc?!",1024);
}
}
2.3 shiro使用密码加密器
2.3.1 测试类
public class Test01 {
public static void main(String[] args) {
//1.获取SecurityManager对象
DefaultSecurityManager securityManager=new DefaultSecurityManager();
// //2.读取ini文件
// IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
//创建Realm对象
MyRealm myRealm=new MyRealm();
//为realm指定加密器
HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");//指定加密算法
credentialsMatcher.setHashIterations(1024);//加密次数
myRealm.setCredentialsMatcher(credentialsMatcher);
//3。设置securityManager的realm
securityManager.setRealm(myRealm);
//4.设置securityManager上下文生效
SecurityUtils.setSecurityManager(securityManager);
//5.获取subject的主体对象
Subject subject=SecurityUtils.getSubject();
try{
//UsernamePasswordToken作用是封装你输入的账号和密码 是客户自己输入的 用来进行比较与realm
UsernamePasswordToken token=new UsernamePasswordToken("hh","123456");
//抛出异常 比对shiro中realm和自己的对比,如果一致则登录成功,不一致则登录失败
subject.login(token);
System.out.println("登陆成功");
}catch(Exception e){
e.printStackTrace();
System.out.println("登陆失败");
}
}
}
2.3.2 修改realm代码
public class MyRealm extends AuthenticatingRealm {
private UserService userService=new UserService();
//该方法用于完成认证的功能
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//根据token获取账号
String username = (String) authenticationToken.getPrincipal();
//根据账号查询用户信息
User user=userService.findByUsername(username);
if(user!=null){
ByteSource credentialsSat = ByteSource.Util.bytes(user.getSalt());
System.out.println(user.getSalt());
//从数据库中获取的密码
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user,user.getPassword(),credentialsSat,this.getName());
//注意返回信息,若没有则会报错
return info;
}
return null;
}
}
2.3.3 对应service
public class UserService {
public User findByUsername(String username) {
if("admin".equals(username)){
return new User(1,username,"小馒头","e18be766ad36f52ea76b2b2c9ad04f8e","abc");
}else if("wd".equals(username)){
return new User(2,username,"孟一","55c23df0ffd3d32d68e83d47f21f7002","efg");
}
return null;
}
}
2.4 自定义realm的授权功能
2.4.1 授权功能
public class MyRealm extends AuthorizingRealm {
private UserService userService = new UserService();
//授权部分
//什么时候执行该方法:当你进行权限校验时会执行该方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) principalCollection.getPrimaryPrincipal();
//根据账号查询该用户具有哪些权限
List<String> list = userService.findPermissionByUsername(user.getName());
if(list!=null&&list.size()>0){
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(list);
return simpleAuthorizationInfo;
}
return null;
}
//认证部分
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.根据token获得账号
String username = (String) authenticationToken.getPrincipal();
//2.根据账号查看用户信息
User user = userService.findByUsername(username);
if(user!=null){
//从数据获取的密码
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user,user.getPassword(),credentialsSalt,this.getName());
return info;
}
return null;
}
}
2.4.2 测试类
public class Test02 {
public static void main(String[] args) {
//1.获取SecurityManager对象
DefaultSecurityManager securityManager=new DefaultSecurityManager();
// //2.读取ini文件
// IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
//创建Realm对象
MyRealm myRealm=new MyRealm();
//为realm指定加密器
HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");//指定加密算法
credentialsMatcher.setHashIterations(1024);//加密次数
myRealm.setCredentialsMatcher(credentialsMatcher);
//3。设置securityManager的realm
securityManager.setRealm(myRealm);
//4.设置securityManager上下文生效
SecurityUtils.setSecurityManager(securityManager);
//5.获取subject的主体对象
Subject subject=SecurityUtils.getSubject();
try{
//UsernamePasswordToken作用是封装你输入的账号和密码 是客户自己输入的 用来进行比较与realm
UsernamePasswordToken token=new UsernamePasswordToken("wd","123456");
//抛出异常 比对shiro中realm和自己的对比,如果一致则登录成功,不一致则登录失败
subject.login(token);
System.out.println(subject.isPermitted("user:query"));
System.out.println(subject.isPermitted("user:update"));
}catch(Exception e){
System.out.println("登陆失败");
}
}
}
2.4.3 对应service类
public class UserService {
public User findByUsername(String username) {
if("admin".equals(username)){
return new User(1,username,"小馒头","e18be766ad36f52ea76b2b2c9ad04f8e","abc");
}else if("wd".equals(username)){
return new User(2,username,"孟一","55c23df0ffd3d32d68e83d47f21f7002","efg");
}
return null;
}
public List<String> findPermissionByUsername(String username){
List<String> list=new ArrayList<>();
if("admin".equals(username)){
list.add("user:query");
list.add("user:update");
list.add("user:insert");
list.add("user:delete");
}else if("wd".equals(username)){
list.add("user:query");
list.add("user:insert");
}
return list;
}
}
2.4.4 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private String realname;
private String password;
private String salt;
}
3.ssm整合shiro
(1)创建一个maven的web工程。
(2)ssm整合到web工程----省略
pom依赖
spring配置文件
web.xml配置文件
(3)整合shiro
3.1 引入shiro的依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</dependency>
3.2 修改spring.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--包扫描-->
<context:component-scan base-package="com.qy151wd.service,com.qy151wd.controller"/>
<!--开启注解-->
<mvc:annotation-driven/>
<!--静态资源的放行-->
<mvc:default-servlet-handler/>
<!--spring的配置-->
<!--数据源配置-->
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<!--驱动名称-->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/project?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!--初始化连接池的个数-->
<property name="initialSize" value="5"/>
<!--至少的个数-->
<property name="minIdle" value="5"/>
<!--最多的个数-->
<property name="maxActive" value="10"/>
<!--最长等待时间单位毫秒-->
<property name="maxWait" value="3000"/>
</bean>
<!--sqlSessionFactory 整合mybatis-->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="ds"/>
<!--设置mybatis映射文件的路径-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!--为dao接口生成代理实现类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
<!--为com.ykq.dao包下的接口生成代理实现类-->
<property name="basePackage" value="com.qy151wd.dao"/>
</bean>
<!--事务管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"/>
</bean>
<!--开启事务管理-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--整合shiro的配置内容-->
<!--①SecurityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="realm"/>
</bean>
<!--创建自定义realm类对象-->
<bean id="realm" class="com.qy151wd.realm.MyRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!--创建密码匹配器-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<property name="hashIterations" value="1024"/>
</bean>
<!-- 启动Shrio的注解 -->
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" />
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<!--shiro过滤工厂: 设置过滤的规则-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="filterChainDefinitions">
<value>
/login/upLogin=anon
/**=authc
</value>
</property>
<property name="filters">
<map>
<entry key="authc">
<bean class="com.qy151wd.filter.LoginFilter"/><!--自定义过滤器所在的路径-->
</entry>
</map>
</property>
</bean>
</beans>
3.3 修改web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encodeFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodeFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- <!– logback 加载那个日志文件–>-->
<!-- <context-param>-->
<!-- <param-name>logbackConfigLocation</param-name>-->
<!-- <!–logback.xml放在resources/config路径下–>-->
<!-- <param-value>classpath:logback.xml</param-value>-->
<!-- </context-param>-->
</web-app>
3.4 权限问题
进入主页后,不同的用户可以看到不同的内容
可以在jsp中获取当前登录者的账号
上面只是在网页中根据不同用户显示不同的菜单,这种方式只能防君子不能防小人。
3.4.1 拦截器
获取请求路径 然后根据你的路径判断当前用户是否具有该权限。
public class LoginFilter extends FormAuthenticationFilter {
//当没有登录时会经过该方法。如果想让他返回json数据那么必须重写该方法
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer=response.getWriter();
CommonResult commonResult=new CommonResult(4001,"未登录",null);
ObjectMapper objectMapper=new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
writer.print(json);//响应给客户json数据
writer.flush();
writer.close();
return false;
}
}
3.4.2 spring整合shiro时提供了一个注解
可以加载相应方法上
1.开启shiro注解
<!-- 启动Shrio的注解 -->
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" />
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
2.使用shiro注解
@RequiresPermissions(value = {"/user/query","/user/aaa"},logical= Logical.OR)
3.4.3 全局变量
@ControllerAdvice
public class MyException {
@ExceptionHandler(value = UnauthorizedException.class)
@ResponseBody
public CommonResult auth(UnauthorizedException e){
e.printStackTrace();
return new CommonResult(4002,"权限不足",null);
}
}
4.ssm整合shiro完成前后端分离
所谓前后端完全分离:后端响应的都是json数据,而不再是网页。
1. 登录成功或者失败应该返回json数据
2. 当未登录时返回的也是json数据
3. 访问未授权的资源,也要分会json。
4.1 登录成功或者失败应该返回json数据
修改登录接口
4.2 当未登录时返回的也是json数据
创建一个过滤器,继承登录校验的某个接口。
public class LoginFilter extends FormAuthenticationFilter {
//当没有登录时会经过该方法。如果想让他返回json数据那么必须重写该方法
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer=response.getWriter();
CommonResult commonResult=new CommonResult(4001,"未登录",null);
ObjectMapper objectMapper=new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
writer.print(json);//响应给客户json数据
writer.flush();
writer.close();
return false;
}
}
注册过滤器 :注意路径
4.3 如果没有权限应该返回json数据
5.关于ssm,shiro整合的例子
5.1 整体框架了解
5.2 controller层
5.2.1 LoginController
@RestController
@RequestMapping("/login")
public class LoginController {
@Autowired
private LoginService loginService;
@GetMapping("/upLogin")
public CommonResult upLogin(String username,String password){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
try{
subject.login(token);
return new CommonResult(200,"登陆成功",null);
}catch (Exception e){
e.printStackTrace();
return new CommonResult(500,"登陆失败",null);
}
}
}
5.2.2 PerController
@RestController
@RequestMapping("/permission")
public class PerController {
//默认spring框架不识别该注解。需要springmvc配置文件中开启该注解
@GetMapping("/query")
@RequiresPermissions(value = {"user:query"},logical = Logical.OR)//使用shiro注解,此处user:query与数据库形式一致
public String query(){
return "user:query";
}
@GetMapping("/update")
@RequiresPermissions(value = {"user:update"})
public String update(){
return "user:update";
}
@GetMapping("/insert")
@RequiresPermissions(value = {"user:insert"})
public String insert(){
return "user:insert";
}
@GetMapping("/delete")
@RequiresPermissions(value = {"user:delete"})
public String delete(){
return "user:delete";
}
@GetMapping("/export")
@RequiresPermissions(value = {"user:export"})
public String export(){
return "user:export";
}
}
5.3 dao包
5.3.1 LoginMapper
public interface LoginMapper {
public User findByName(String username);
public List<String> findPrimaryById(Integer userid);
}
5.4 entity类
5.4.1 User
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer userid;
private String username;
private String userpwd;
private String sex;
private String address;
private String salt;
}
5.5 filter
5.5.1 LoginFilter
public class LoginFilter extends FormAuthenticationFilter {
//当没有登录时会经过该方法。如果想让他返回json数据那么必须重写该方法
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer=response.getWriter();
CommonResult commonResult=new CommonResult(4001,"未登录",null);
ObjectMapper objectMapper=new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
writer.print(json);//响应给客户json数据
writer.flush();
writer.close();
return false;
}
}
5.6 handle
5.6.1 MyException
@ControllerAdvice
public class MyException {
@ExceptionHandler(value = UnauthorizedException.class)
@ResponseBody
public CommonResult auth(UnauthorizedException e){
e.printStackTrace();
return new CommonResult(4002,"权限不足",null);
}
}
5.7 realm
5.7.1 MyRealm
public class MyRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) principalCollection.getPrimaryPrincipal();
//根据该账号查询用户有哪些权限
List<String> primary = loginService.findPrimaryById(user.getUserid());
System.out.println(primary);
if(primary!=null&&primary.size()>0){
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
info.addStringPermissions(primary);
return info;
}
return null;
}
//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String user = (String) authenticationToken.getPrincipal();
User byName = loginService.findByName(user);
if(byName!=null){
ByteSource source = ByteSource.Util.bytes(byName.getSalt());
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(byName,byName.getUserpwd(),source,this.getName());
return info;
}
return null;
}
}
5.8 service
5.8.1 LoginService
public interface LoginService {
public User findByName(String username);
public List<String> findPrimaryById(Integer userid);
}
5.8.2 LoginServiceImpl
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private LoginMapper loginMapper;
public User findByName(String username) {
User name = loginMapper.findByName(username);
return name;
}
public List<String> findPrimaryById(Integer userid) {
List<String> list = loginMapper.findPrimaryById(userid);
return list;
}
}
5.9 util
5.9.1 CommonResult
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult {
private Integer code;
private String msg;
private Object data;
}
5.10 mapper
5.10.1 loginMapper.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.qy151wd.dao.LoginMapper">
<sql id="userContent">
userid,username,userpwd,sex,address,salt
</sql>
<select id="findByName" parameterType="string" resultType="com.qy151wd.entity.User">
select <include refid="userContent"/> from user where username=#{username}
</select>
<update id="updateById">
update user
<set>
<if test="username!=null and username!=''">
username=#{username},
</if>
<if test="userpwd!=null and userpwd!=''">
userpwd=#{userpwd},
</if>
<if test="sex!=null and sex!=''">
sex=#{sex},
</if>
<if test="address!=null and address!=''">
address=#{address},
</if>
<if test="salt!=null and salt!=''">
salt=#{salt},
</if>
</set>
where userid=#{userid}
</update>
<insert id="insertUser">
insert into user(userid,username,userpwd,sex,address,salt)
values (null,#{username},#{userpwd},#{sex},#{address},#{salt})
</insert>
<delete id="deleteById">
delete from user where userid=#{userid}
</delete>
<select id="findPrimaryById" resultType="string">
select percode from
user_role ur join role_permission rp on ur.roleid=rp.roleid
join permission p on p.perid=rp.perid
where ur.userid=#{userid};
</select>
</mapper>
5.11 spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--包扫描-->
<context:component-scan base-package="com.qy151wd.service,com.qy151wd.controller"/>
<!--开启注解-->
<mvc:annotation-driven/>
<!--静态资源的放行-->
<mvc:default-servlet-handler/>
<!--spring的配置-->
<!--数据源配置-->
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<!--驱动名称-->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/project?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!--初始化连接池的个数-->
<property name="initialSize" value="5"/>
<!--至少的个数-->
<property name="minIdle" value="5"/>
<!--最多的个数-->
<property name="maxActive" value="10"/>
<!--最长等待时间单位毫秒-->
<property name="maxWait" value="3000"/>
</bean>
<!--sqlSessionFactory 整合mybatis-->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="ds"/>
<!--设置mybatis映射文件的路径-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!--为dao接口生成代理实现类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
<!--为com.ykq.dao包下的接口生成代理实现类-->
<property name="basePackage" value="com.qy151wd.dao"/>
</bean>
<!--事务管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"/>
</bean>
<!--开启事务管理-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--整合shiro的配置内容-->
<!--①SecurityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="realm"/>
</bean>
<!--创建自定义realm类对象-->
<bean id="realm" class="com.qy151wd.realm.MyRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!--创建密码匹配器-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<property name="hashIterations" value="1024"/>
</bean>
<!-- 启动Shrio的注解 -->
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" />
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<!--shiro过滤工厂: 设置过滤的规则-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="filterChainDefinitions">
<value>
/login/upLogin=anon
/**=authc
</value>
</property>
<property name="filters">
<map>
<entry key="authc">
<bean class="com.qy151wd.filter.LoginFilter"/><!--自定义过滤器所在的路径-->
</entry>
</map>
</property>
</bean>
</beans>
5.12 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encodeFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodeFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- <!– logback 加载那个日志文件–>-->
<!-- <context-param>-->
<!-- <param-name>logbackConfigLocation</param-name>-->
<!-- <!–logback.xml放在resources/config路径下–>-->
<!-- <param-value>classpath:logback.xml</param-value>-->
<!-- </context-param>-->
</web-app>
5.13 依赖
<dependencies>
<!--spring-webmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--mybatis和spring整合的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!--druid连接池依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.1</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!--jackson java对象转换为json对象 @ResponseBody-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2.2</version>
</dependency>
<!--servlet-api依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<!--generator-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</dependency>
</dependencies>
6.springBoot整合shiro
6.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.qy151wd</groupId>
<artifactId>springboot_shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入swagger依赖-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.7.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
6.2 application配置文件
#数据源
spring.datasource.url=jdbc:mysql://localhost:3306/project?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#端口号
server.port=8808
#sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
6.3 创建shiro配置类
@Configuration
public class ShiroConfig {
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(realm());
return securityManager;
}
@Bean
public Realm realm(){
MyRealm myRealm=new MyRealm();
myRealm.setCredentialsMatcher(credentialsMatcher());
return myRealm;
}
@Bean
public CredentialsMatcher credentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
@Bean(value = "shiroFilter")
public ShiroFilterFactoryBean filterFactoryBean(){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager());
//设置拦截规则
HashMap<String,String> map=new HashMap<>();
map.put("/login/upLogin","anon");
map.put("/**/*.css","anon");
map.put("/**/*.js","anon");
map.put("/doc.html","anon");
map.put("/swagger-resources","anon");
map.put("/v2/api-docs","anon");
map.put("/**","authc");
factoryBean.setFilterChainDefinitionMap(map);
//设置自定义认证过滤器
HashMap<String, Filter> filterMap=new HashMap<String, Filter>();
filterMap.put("authc",new LoginFilter());
factoryBean.setFilters(filterMap);
return factoryBean;
}
@Bean //注册filter
public FilterRegistrationBean<Filter> filterRegistrationBean(){
FilterRegistrationBean<Filter> filterRegistrationBean=new FilterRegistrationBean<>();
filterRegistrationBean.setName("shiroFilter");
filterRegistrationBean.setFilter(new DelegatingFilterProxy());
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
//开始shiro注解
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
}
注意: (此处不进行修改会报错)
6.4 创建controller service dao entity(与ssm一致)
6.5 创建realm
public class MyRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) principalCollection.getPrimaryPrincipal();
//根据该账号查询用户有哪些权限
List<String> primary = loginService.findPrimaryById(user.getUserid());
System.out.println(primary);
if(primary!=null&&primary.size()>0){
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
info.addStringPermissions(primary);
return info;
}
return null;
}
//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String user = (String) authenticationToken.getPrincipal();
User byName = loginService.findByName(user);
if(byName!=null){
ByteSource source = ByteSource.Util.bytes(byName.getSalt());
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(byName,byName.getUserpwd(),source,this.getName());
return info;
}
return null;
}
}
开启shiro注解
//开始shiro注解
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
6.6 使用swagger进行登录测试和权限测试
doc.html无法访问以及样式无效。 shiro拦截规则拦截了 .
6.6.1 引入swigger依赖
上边所给的依赖已经引入
<!--引入swagger依赖--> <dependency> <groupId>com.spring4all</groupId> <artifactId>swagger-spring-boot-starter</artifactId> <version>1.9.1.RELEASE</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.7.8</version> </dependency>
6.6.2 配置swigger
@Configuration
public class SwaggerConfig {
@Bean//swagger中所有的功能都封装在Docket类中
public Docket docket(){
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.host("localhost:8808")
.apiInfo(apiInfo())//设置api文档信息
.select()
.apis(RequestHandlerSelectors.basePackage("com.qy151wd.controller"))
.build();
return docket;
}
//定义自己接口文档信息
private ApiInfo apiInfo(){
Contact DEFAULT_CONTACT = new Contact("小可", "http://www.baidu.com", "[email protected]");
ApiInfo apiInfo = new ApiInfo("萌萌的小项目", "适合小新手", "V1.0", "http://www.jd.com",
DEFAULT_CONTACT, "牛牛公司", "http://www.taobao.com", new ArrayList<VendorExtension>());
return apiInfo;
}
}
6.6.3 释放swigger资源
上述配置shiro也已经释放此处的静态swigger资源
6.6.4 开启swigger注解
6.6.5 测试
在页面上输入localhost:对应端口号/doc.html,可直接查看,结果为