学习项目2025-02
学习项目2025-02-04
1.文档:
https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/#http
2.SecurityConfig:
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 批准、准许 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { // 参照父类WebSecurityConfigurerAdapter里 // protected void configure(HttpSecurity http) throws Exception{}方法的实现代码 // 链式编程 // authorization 批准、准许 // 请求授权的规则 http.authorizeRequests() // 对首页允许所有人访问 .antMatchers("/").permitAll() // 对其他页面要校验是否有对应角色 .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); // 没有权限默认跳登录页,需开启登录的页面 http.formLogin(); } /** * 认证后授权 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 参照父类WebSecurityConfigurerAdapter里 // protected void configure(AuthenticationManagerBuilder auth) throws Exception{}方法的注释 // 内存中验证,后续改为数据库验证 // 直接写明文密码.password("12") // 浏览器控制台报错There was an unexpected error (type=Internal Server Error, status=500)., // idea里报错java.lang.IllegalArgumentException: // There is no PasswordEncoder mapped for the id "null" // spring security 5.0+ 提供很多加密方法。 // 进行硬加密。密码编码:passwordEncoder // auth.inMemoryAuthentication() auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) // 用户名、密码->角色 // .withUser("qinjiang").password("11").roles(new String[]{"vip2","vip3"}) // 可变长参数 public UserDetailsBuilder roles(String... roles) {} .withUser("qinjiang").password(new BCryptPasswordEncoder().encode("11")).roles("vip2","vip3") // 追加用户 .and() .withUser("root").password(new BCryptPasswordEncoder().encode("22")).roles("vip1","vip2","vip3") .and() .withUser("guest").password(new BCryptPasswordEncoder().encode("33")).roles("vip1"); } }
学习项目2025-02-06
1.项目用到前端UI:Semantic UI
2.authentication:认证
authorization:授权
3.先用thymeleaf和security的最新包
4.加上注销
SecurityConfig.java里:
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 批准、准许 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { // 参照父类WebSecurityConfigurerAdapter里 // protected void configure(HttpSecurity http) throws Exception{}方法的实现代码 // 链式编程 // authorization 批准、准许 // 请求授权的规则 http.authorizeRequests() // 对首页允许所有人访问 .antMatchers("/").permitAll() // 对其他页面要校验是否有对应角色 .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); // 没有权限默认跳登录页,需开启登录的页面 http.formLogin(); // 注销 // http.logout(); // 注销、清cookie、HttpSession、注销成功后跳首页(而不是默认的登录页) http.logout().deleteCookies().invalidateHttpSession(true).logoutSuccessUrl("/"); } }
首页index.html:
<!--未登录--> <a class="item" th:href="@{/toLogin}"> <i class="address card icon"></i> 登录 </a> <!--已登录--> <a class="item" th:href="@{/logout}"> <!-- https://semantic-ui.com/elements/icon.html --> <i class="sign-out icon"></i> 注销 </a>
学习项目2025-02-07、2025-02-08
1.根据登录未登录分别显示用户名、注销及登录:
1.1pom里引入thymeleaf-extras-springsecurity5:
<!-- thymeleaf-springsecurity整合包 --> <!-- 一开始按教程用的springsecurity4,运行发现“登录”、“注销”都显示出来了,有问题, 所以将视频里pom引入的thymeleaf-extras-springsecurity4改为用thymeleaf-extras-springsecurity5, 同时index.html里命名空间由视频里的xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4 改为xmlns:sec="http://www.thymeleaf.org/extras/spring-security"--> <!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency>
1.2首页index.html:
1.2.1命名空间:
<!DOCTYPE html> <!-- 一开始按教程用的springsecurity4,运行发现“登录”、“注销”都显示出来了,有问题, 所以将视频里pom引入的thymeleaf-extras-springsecurity4改为用thymeleaf-extras-springsecurity5, 同时index.html里命名空间由视频里的xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4 改为xmlns:sec="http://www.thymeleaf.org/extras/spring-security"--> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
1.2.2根据登录未登录分别显示用户名、注销及登录:
<!--登录注销--> <div class="right menu"> <!--未登录--> <div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/toLogin}"> <i class="address card icon"></i> 登录 </a> </div> <!--已登录--> <div sec:authorize="isAuthenticated()"> <a class="item"> 用户名:<span sec:authentication="name"></span> <!-- 这里按原教程为sec:authentication="principal.getAuthorities()", 在thymeleaf-extras-springsecurity5中没这方法了,最终导致例如登录后点击level-1-1然后让登录后页面报500错误, 这错误实际是由于登录后这里principal.getAuthorities()获取不到角色导致的, 解决方案:改为用sec:authentication="principal.authorities" --> 角色:<span sec:authentication="principal.authorities"></span> </a> </div> <div sec:authorize="isAuthenticated()"> <a class="item" th:href="@{/logout}"> <!-- https://semantic-ui.com/elements/icon.html --> <i class="sign-out icon"></i> 注销 </a> </div> </div>
2.菜单根据用户的角色动态显示:index.html:
<div class="column" sec:authorize="hasRole('vip1')"> <!-- 省略level1具体菜单 --> </div> <div class="column" sec:authorize="hasRole('vip2')"> <!-- 省略level2具体菜单 --> </div> <div class="column" sec:authorize="hasRole('vip3')"> <!-- 省略level3具体菜单 --> </div>
3.在默认的/login页面,加“记住我”:
SecurityConfig.java里:
@Override protected void configure(HttpSecurity http) throws Exception { // 参照父类WebSecurityConfigurerAdapter里 // protected void configure(HttpSecurity http) throws Exception{}方法的实现代码 // 链式编程 // authorization 批准、准许 // 请求授权的规则 http.authorizeRequests() // 对首页允许所有人访问 .antMatchers("/").permitAll() // 对其他页面要校验是否有对应角色 .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); // 没有权限默认跳登录页,需开启登录的页面 http.formLogin(); // 注销 // http.logout(); // 注销、清cookie、HttpSession、注销成功后跳首页(而不是默认的登录页) // http.logout().deleteCookies("remove").invalidateHttpSession(true).logoutSuccessUrl("/"); http.logout().logoutSuccessUrl("/"); // 开启记住我功能,本质是cookie,以remember-me为name存了一个cookie,默认保存两周,关闭浏览器后再次打开浏览器该cookie仍会存在 http.rememberMe(); }
学习项目2025-02-09
1.首页定制:用新写的登录页替换框架自带的登录页(样式):
1.1loginPage()的默认值是/login,即默认跳springsecurity提供的/login跳转的登录页,我们为了用自定义更美观的样式,跳/toLogin,跳views/login.html页面。设置loginPage("/toLogin")后,再请求 http://localhost:8080/login 就会报404,因为不再用默认的/login来跳登录页了
1.2对应于login.html里的<form th:action="@{/login}" method="post">,SecurityConfig.java里设置用loginProcessingUrl("/login")来处理登录
1.3指出用户名、密码参数在前端的name,
1.4/toLogin页面里加上“记住我”:SecurityConfig.java里设置http.rememberMe().rememberMeParameter("rememberme")指明前端参数的name为rememberme(即本例中name为rememberme的checkbox)
SecurityConfig.java:
@Override protected void configure(HttpSecurity http) throws Exception { // 参照父类WebSecurityConfigurerAdapter里 // protected void configure(HttpSecurity http) throws Exception{}方法的实现代码 // 链式编程 // authorization 批准、准许 // 请求授权的规则 http.authorizeRequests() // 对首页允许所有人访问 .antMatchers("/").permitAll() // 对其他页面要校验是否有对应角色 .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); // 没有权限默认跳登录页,需开启登录的页面 // loginPage()的默认值是/login,即默认跳springsecurity提供的/login跳转的登录页, // 我们为了用自定义更美观的样式,跳/toLogin,跳views/login.html页面 // 设置loginPage("/toLogin")后,再请求http://localhost:8080/login就会报404,因为不再用默认的/login来跳登录页了 http.formLogin().loginPage("/toLogin") // 指出username参数在前端name为user,password参数在前端name为pwd .usernameParameter("user") .passwordParameter("pwd") // 对应于login.html里的<form th:action="@{/login}" method="post">,SecurityConfig.java里设置用loginProcessingUrl("/login")来处理登录 .loginProcessingUrl("/login"); // 注销 // http.logout(); // 注销、清cookie、HttpSession、注销成功后跳首页(而不是默认的登录页) // http.logout().deleteCookies("remove").invalidateHttpSession(true).logoutSuccessUrl("/"); // 解决点“注销”报错404的问题:注销/logout是get请求,不安全,所以需要关闭springboot默认开启的CSRF(cross-site request forgery跨站请求伪造) // 关闭CSRF功能 http.csrf().disable(); http.logout().logoutSuccessUrl("/"); // 开启记住我功能,本质是cookie,以remember-me为name存了一个cookie,默认保存两周,关闭浏览器后再次打开浏览器该cookie仍会存在 // http.rememberMe(); // .rememberMeParameter("rememberme")指明前端参数的name为rememberme(即本例中name为rememberme的checkbox) http.rememberMe().rememberMeParameter("rememberme"); }
login.html:
<!-- SecurityConfig.java里设置 http.formLogin().loginPage("/toLogin");后, 输入用户名密码登录后,浏览器地址栏为http://localhost:8080/usr/login,报错404, 是因为并没有/uer/login这个请求,所以这里form的action改为使用/toLogin 或springsecurity提供的/login(这时需要在SecurityConfig.java里设置http.formLogin().loginPage("/toLogin") .loginProcessingUrl("/login");)--> <!-- <form th:action="@{/usr/login}" method="post">--> <!-- <form th:action="@{/toLogin}" method="post">--> <form th:action="@{/login}" method="post"> <div class="field"> <label>Username</label> <div class="ui left icon input"> <!-- <input type="text" placeholder="Username" name="username">--> <!-- http.formLogin().loginPage("/toLogin") // 指出username参数在前端name为user,password参数在前端name为pwd .usernameParameter("user") .passwordParameter("pwd") .loginProcessingUrl("/login"); --> <input type="text" placeholder="Username" name="user"> <i class="user icon"></i> </div> </div> <div class="field"> <label>Password</label> <div class="ui left icon input"> <!-- <input type="password" name="password">--> <!-- http.formLogin().loginPage("/toLogin") // 指出username参数在前端name为user,password参数在前端name为pwd .usernameParameter("user") .passwordParameter("pwd") .loginProcessingUrl("/login"); --> <input type="password" name="pwd"> <i class="lock icon"></i> </div> </div> <div class="field"><!--class="field"样式实现居中效果--> <input type="checkbox" name="rememberme"> 记住我 </div> <input type="submit" class="ui blue submit button"/> </form>
2.解决注销报404问题:
// 解决点“注销”报错404的问题:注销/logout是get请求,不安全,所以需要关闭springboot默认开启的CSRF(cross-site request forgery跨站请求伪造) // 关闭CSRF功能 http.csrf().disable(); http.logout().logoutSuccessUrl("/");
学习项目2025-02-11、2025-02-15
1.shiro快速入门:shiro官方demo:quickstart:
1.1导入依赖
<dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.10.0</version> </dependency> <!-- configure logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
1.2配置参数:配置log4j.properties和shiro.ini:参照官方demo及狂神代码;
1.3运行quickstart.java:运行并查看输出,理解代码,代码是官方demo不是自己写的这里就不粘贴了。
学习项目2025-02-16
realm /relm/
1.springboot整合shiro环境搭建
1.1新建spring项目,选web模块依赖
1.2pom里导入thymeleaf、建首页index.html里头文件设置themeleaf空间
1.3新建shiroConfig,需@Bean依次注入realm(自定义类)、DefaultWebSecurityManager、ShiroFilterFactoryBean。
1.4建用户新增、编辑页、首页加俩跳转链接。
2.shiro实现登录拦截
2.1anonymous /əˈnɒn.ɪ.məs/ 匿名的
代码:
pom.xml:
<!-- 一开始我shiro-spring用的是1.10.0,测试不能拦截/user/add和/user/update, 按狂神老师视频改用1.4.1后,可以正确拦截了--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
index.html:
<!DOCTYPE html> <!-- index.html应在resources/templates文件夹下,否则一开始放在static文件夹下,访问localhost:8080报错: org.thymeleaf.exceptions.TemplateInputException: Error resolving template [index], template might not exist or might not be accessible by any of the configured Template Resolvers --> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> <p th:text="${msg}"> <!-- 经测试:超链接上方不加<hr>就无法显示超链接,加上<hr>后俩超链接能正常显示 --> <hr> <a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a> </body> </html>
add.hmtl:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>add</h1> </body> </html>
update.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>update</h1> </body> </html>
MyController.java:
@Controller public class MyController { @RequestMapping({"/", "/index"}) public String toIndex(Model model) { model.addAttribute("msg", "hello, shiro!"); return "index"; } @RequestMapping("/user/add") public String goUserAdd() { return "user/add"; } @RequestMapping("/user/update") public String goUserUpdate() { return "user/update"; } }
UserRealm.java:
public class UserRealm extends AuthorizingRealm { // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=>授权doGetAuthorizationInfo方法"); return null; } // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthenticationInfo方法"); return null; } }
ShiroConfig.java:
@Configuration public class ShiroConfig { /** * ShiroFilterFactoryBean:第3步 * * 默认是通过方法名getDefaultWebSecurityManager获取到defaultWebSecurityManager对象 * 这里为了getShiroFilterFactoryBean签名获取方便, * 所以对getDefaultWebSecurityManager方法:@Bean设置别名securityManager * @return */ @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean( @Qualifier("securityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager); /** * shiro设置过滤器 * * anon:无需认证就可以访问,即anonymous(该单词意为:匿名的) * authc:必须认证了才能访问,即authentication * user:必须拥有 记住我 功能才能访问 * perms:拥有对某个资源的权限才能访问 * roles:具有某个角色权限才能访问 */ Map<String, String> filterMap = new LinkedHashMap<>(); // 设置/user/add、/user/update请求,需登录才能访问 filterMap.put("/user/add", "authc"); filterMap.put("/user/update", "authc"); // 过滤链 bean.setFilterChainDefinitionMap(filterMap); return bean; } /** * DefaultWebSecurityManager:第2步 * * userRealm()方法已加@Bean注解,这里加上@Qualifier("userRealm")注解, * 就可以通过userRealm()方法(名)获取到userRealm对象 * @param userRealm * @return */ @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager( @Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; } /** * 创建realm对象,需自定义类:第1步 * @return */ @Bean public UserRealm userRealm() { return new UserRealm(); } }
学习项目2025-02-17
1.拦截后跳登录页:写个登录页,Controller里加上登录页跳转请求,然后设置鉴权失败时跳登录页的url:
1.1登录页:login.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录</h1> <form action=""> <p>用户名:<input type="text" name="username"></p> <p>密码:<input type="password" name="password"></p> <p><input type="submit"></p> </form> </body> </html>
1.2MyController里跳登录页的请求:
@RequestMapping("/toLoginPage")
public String toLoginPage() {
return "login";
}
1.3设置鉴权失败时跳登录页的url:
@Configuration public class ShiroConfig { /** * ShiroFilterFactoryBean:第3步 * * 默认是通过方法名getDefaultWebSecurityManager获取到defaultWebSecurityManager对象 * 这里为了getShiroFilterFactoryBean签名获取方便, * 所以对getDefaultWebSecurityManager方法:@Bean设置别名securityManager * @return */ @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean( @Qualifier("securityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager); /** * shiro设置过滤器 * * anon:无需认证就可以访问,即anonymous(该单词意为:匿名的) * authc:必须认证了才能访问,即authentication * user:必须拥有 记住我 功能才能访问 * perms:拥有对某个资源的权限才能访问 * roles:具有某个角色权限才能访问 */ Map<String, String> filterMap = new LinkedHashMap<>(); // 设置/user/add、/user/update请求,需登录才能访问 filterMap.put("/user/add", "authc"); filterMap.put("/user/update", "authc"); // 过滤链 bean.setFilterChainDefinitionMap(filterMap); // 设置因未登录而拦截后跳转登录页的请求 bean.setLoginUrl("/toLoginPage"); return bean; } // 其余代码省略 }
2.Shiro实现用户认证
2.1MyController里加login方法:获取用户输入的用户名、密码,封装用户登录信息并执行登录;
2.2login.html里form的action配上login接口;加上显示错误信息的标签;
2.3UserRealm里认证方法加上对用户名、密码认证的代码;
3.代码:
3.1MyController.java里加login方法:
/** * 登录方法 * * 参照Quickstart进行登录,并捕获异常 * @param username * @param password * @param model * @return */ @RequestMapping("/login") public String login(String username, String password, Model model) { // 获取当前用户 Subject subject = SecurityUtils.getSubject(); // 封装用户登录信息 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { // 执行登录,该方法已全部封装了,如果没有异常就说明OK了 subject.login(token); // 跳首页 return "index"; } catch (UnknownAccountException uae) { // 用户名不存在 model.addAttribute("msg", "用户名不存在"); // 跳登录页 return "login"; } catch (IncorrectCredentialsException ice) { // 密码错误 model.addAttribute("msg", "密码错误"); // 跳登录页 return "login"; } }
3.2login.html里form的action配上login接口;加上显示错误信息的标签;
<h1>登录</h1> <hr> <p th:text="${msg}" style="color: red"></p> <form th:action="@{/login}"> <p>用户名:<input type="text" name="username"></p> <p>密码:<input type="password" name="password"></p> <p><input type="submit"></p> </form>
3.3UserRealm里认证方法加上对用户名、密码认证的代码;
// 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthenticationInfo方法"); // 模拟正确的 用户名、密码,后续改为从数据库读取 String correctUsername = "root"; String correctPassword = "123456"; // 转成我们能进行读取的UsernamePasswordToken UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; if (!correctUsername.equals(userToken.getUsername())) { // return null,即用户不存在,后续会被自动识别为UnknownAccountException return null; } // 密码认证,Shiro进行(无需我们处理,这样更安全) // 该doGetAuthenticationInfo方法声明,返回值是AuthenticationInfo,是一个接口, // 我们返回AuthenticationInfo接口的实现类:SimpleAuthenticationInfo // SimpleAuthenticationInfo的构造函数声明: // principal:认证主体的标识,代表当前被认证的用户或实体的唯一标识符; // credentials:密码的对象; // realmName:用户名 // public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {} return new SimpleAuthenticationInfo("", correctPassword, ""); }
学习项目2025-02-18
今天没具体编码练习,只是记录了视频讲的操作步骤:
1.Shiro整合MyBatis
1.1导入依赖:mysql(mysql-connector-java)、log4j、druid、MyBatis(mybatis-spring-boot-start)、lombok
1.2创建配置文件application.yml,然后application.properties里配置mybatis
1.3写POJO、接口Mapper、写Mapper配置文件xml、写Service层、实现类
1.4测试类中测试连接数据库是否成功、将UserRealm里原认证代码改为从数据库读取
学习项目2025-02-24
1.Shiro整合MyBatis
1.1pom.xml补充依赖:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <!-- mybatis-spring-boot-start这里要用2.x.x否则测试会报错: Caused by: org.springframework.beans.factory. NoSuchBeanDefinitionException: No qualifying bean of type 'com.kuang.mapper.UserMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} --> <!-- <version>3.0.4</version>--> <version>2.1.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
1.2User.java:
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; }
1.3UserMapper.java
@Repository @Mapper public interface UserMapper { public User getUserByName(String name); }
1.4UserMapper.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"> <!-- 配置namespace --> <mapper namespace="com.kuang.mapper.UserMapper"> <select id="getUserByName" parameterType="String" resultType="User"> select * from mybatis.user where name = #{name} </select> </mapper>
1.5UserService:
public interface UserService { public User getUserByName(String name); }
1.6UserServiceImpl:
@Service public class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Override public User getUserByName(String name) { return userMapper.getUserByName(name); } }
1.7ShiroSpringbootApplicationTests.java里验证环境:
@SpringBootTest class ShiroSpringbootApplicationTests { @Autowired UserService userService; @Test void contextLoads() { User user = userService.getUserByName("kTest222"); System.out.println("==============user: " + user); } }
1.8UserRealm.java里认证方法doGetAuthenticationInfo改为从数据库读取:
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthenticationInfo方法"); // 模拟正确的 用户名、密码,后续改为从数据库读取 // String correctUsername = "root"; // String correctPassword = "123456"; // 转成我们能进行读取的UsernamePasswordToken UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; // if (!correctUsername.equals(userToken.getUsername())) { // // return null,即用户不存在,后续会被自动识别为UnknownAccountException // return null; // } // 数据库读取 User user = userService.getUserByName(userToken.getUsername()); if (user == null) { // 用户不存在 return null; } // 密码认证,Shiro进行(无需我们处理,这样更安全) // 该doGetAuthenticationInfo方法声明,返回值是AuthenticationInfo,是一个接口, // 我们返回AuthenticationInfo接口的实现类:SimpleAuthenticationInfo // SimpleAuthenticationInfo的构造函数声明: // principal:认证主体的标识,代表当前被认证的用户或实体的唯一标识符; // credentials:密码的对象; // realmName:用户名 // public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {} // return new SimpleAuthenticationInfo("", correctPassword, ""); return new SimpleAuthenticationInfo("", user.getPwd(), ""); }
学习项目2025-02-26
1.Shiro请求授权实现
1.1直接在ShiroConfig的getShiroFilterFactoryBean方法中对/user/add请求进行限制,需要具有某权限才能访问,进行验证;
补充:同时加上:在MyController里加上未授权时跳转的请求路径,然后在ShiroConfig里设置未授权时跳转的请求路径,实现未授权时不提示默认401,而是跳指定的页面(本例由/jumpUnauthorizedPage指定)
MyController里加未授权时跳转的请求:
/** * 展示未授权提示信息 * @return */ @RequestMapping("/jumpUnauthorizedPage") @ResponseBody public String jumpUnauthorizedPage() { return "未经授权无法访问此页面"; }
ShiroConfig里加权限过滤:
/** * ShiroFilterFactoryBean:第3步 * * 默认是通过方法名getDefaultWebSecurityManager获取到defaultWebSecurityManager对象 * 这里为了getShiroFilterFactoryBean签名获取方便, * 所以对getDefaultWebSecurityManager方法:@Bean设置别名securityManager * @return */ @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean( @Qualifier("securityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager); /** * shiro设置过滤器 * * anon:无需认证就可以访问,即anonymous(该单词意为:匿名的) * authc:必须认证了才能访问,即authentication * user:必须拥有 记住我 功能才能访问 * perms:拥有对某个资源的权限才能访问 * roles:具有某个角色权限才能访问 */ Map<String, String> filterMap = new LinkedHashMap<>(); // 设置需要有user:add权限,才能访问/user/add资源 // 一开始这种写法不行,因为filterMap是Map且上面使用的是LinkedHashMap(它保留了插入顺序), // 所以后续的value即authc会覆盖前面的value即perms[user:add] // filterMap.put("/user/add", "perms[user:add]"); // 设置/user/add请求,需登录才能访问 // filterMap.put("/user/add", "authc"); // 改用shiro的过滤器链语法来组合多个过滤器 filterMap.put("/user/add", "authc, perms[user:add]"); // 设置/user/update请求,需登录才能访问 // filterMap.put("/user/update", "authc"); filterMap.put("/user/update", "authc, perms[user:update]"); // 过滤链 bean.setFilterChainDefinitionMap(filterMap); // 设置因未登录而拦截后跳转登录页的请求 bean.setLoginUrl("/toLoginPage"); // 设置未授权时跳转页面的请求路径 bean.setUnauthorizedUrl("/jumpUnauthorizedPage"); return bean; }
1.2在UserRealm的doGetAuthorizationInfo授权方法中添加授权,使所有走doGetAuthorizationInfo方法的请求都能具有权限通过验证;
// 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=>授权doGetAuthorizationInfo方法"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 添加授权 info.addStringPermission("user:add"); // return null; return info; }
1.3改为从数据库读取权限:数据库User表加perms字段;UserRealm的doGetAuthenticationInfo认证方法里获取用户时,将用户作为第一个参数即pricipal(用户身份标识)传给SimpleAuthenticationInfo对象构造函数;然后UserRealm的doGetAuthorizationInfo授权方法里就可以通过SecurityUtils.getSubject()获取subject,再subject.getPricipal()获取pricipal用户身份标识,强转为User类型,然后就可以获取权限,并授权(通过授权信息SimpleAuthorizationInfo)
User.java:
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; private String permission; }
UserRealm.java:
public class UserRealm extends AuthorizingRealm { @Autowired UserService userService; // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=>授权doGetAuthorizationInfo方法"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 添加授权 // info.addStringPermission("user:add"); // 一般将SecurityUtils.getSubject()获取的subject对象命名为currentUser Subject currentUser = SecurityUtils.getSubject(); User user = (User) currentUser.getPrincipal(); info.addStringPermission(user.getPermission()); // return null; return info; } // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthenticationInfo方法"); // 模拟正确的 用户名、密码,后续改为从数据库读取 // String correctUsername = "root"; // String correctPassword = "123456"; // 转成我们能进行读取的UsernamePasswordToken UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; // if (!correctUsername.equals(userToken.getUsername())) { // // return null,即用户不存在,后续会被自动识别为UnknownAccountException // return null; // } // 数据库读取 User user = userService.getUserByName(userToken.getUsername()); if (user == null) { // 用户不存在 return null; } // 密码认证,Shiro进行(无需我们处理,这样更安全) // 该doGetAuthenticationInfo方法声明,返回值是AuthenticationInfo,是一个接口, // 我们返回AuthenticationInfo接口的实现类:SimpleAuthenticationInfo // SimpleAuthenticationInfo的构造函数声明: // principal:认证主体的标识,代表当前被认证的用户或实体的唯一标识符; // credentials:密码的对象; // realmName:用户名 // public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {} // return new SimpleAuthenticationInfo("", correctPassword, ""); // return new SimpleAuthenticationInfo("", user.getPwd(), ""); return new SimpleAuthenticationInfo(user, user.getPwd(), ""); } }
学习项目2025-02-27
1.shiro整合thymeleaf
1.1导入shiro整合thymeleaf依赖,依赖格式参照springsecurity整合thymeleaf依赖格式,版本号用的maven repository上最新版
1.2配置shiro方言,在ShiroConfig里配置获取ShiroDialect
1.3改首页index.html,加shiro:标签,要导入命名空间,参照springsecurity整合thymeleaf时命名空间,用shiro:hasPermission标签
1.4重启项目验证确实首页没按钮了,新加登录按钮,验证登录后是否显示add、update按钮
1.5控制登录后不应该显示“登录”按钮,用th:if=${session.loginUser==null},要在登录认证后将user存到session中(shiro实现的session),该session通过subject.getSession()来获取。
2.具体代码:
pom.xml:
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
ShiroConfig.java:
/** * 创建Shiro方言,用于shiro整合thymeleaf * @return */ @Bean public ShiroDialect getShiroDialect() { return new ShiroDialect(); }
UserRealm.java:
// 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthenticationInfo方法"); // 模拟正确的 用户名、密码,后续改为从数据库读取 // String correctUsername = "root"; // String correctPassword = "123456"; // 转成我们能进行读取的UsernamePasswordToken UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; // if (!correctUsername.equals(userToken.getUsername())) { // // return null,即用户不存在,后续会被自动识别为UnknownAccountException // return null; // } // 数据库读取 User user = userService.getUserByName(userToken.getUsername()); if (user == null) { // 用户不存在 return null; } // 将用户信息存入session(shiro实现的session), // 用于首页th:if判断session中是否含有用户信息来判断是否已登录,控制是否显示“登录”按钮 Subject currentSubject = SecurityUtils.getSubject(); Session session = currentSubject.getSession(); session.setAttribute("loginUser", user); // 密码认证,Shiro进行(无需我们处理,这样更安全) // 该doGetAuthenticationInfo方法声明,返回值是AuthenticationInfo,是一个接口, // 我们返回AuthenticationInfo接口的实现类:SimpleAuthenticationInfo // SimpleAuthenticationInfo的构造函数声明: // principal:认证主体的标识,代表当前被认证的用户或实体的唯一标识符; // credentials:密码的对象; // realmName:用户名 // public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {} // return new SimpleAuthenticationInfo("", correctPassword, ""); // return new SimpleAuthenticationInfo("", user.getPwd(), ""); return new SimpleAuthenticationInfo(user, user.getPwd(), ""); }
index.html
<!DOCTYPE html> <!-- index.html应在resources/templates文件夹下,否则一开始放在static文件夹下,访问localhost:8080报错: org.thymeleaf.exceptions.TemplateInputException: Error resolving template [index], template might not exist or might not be accessible by any of the configured Template Resolvers --> <html xmlns:th="http://www.thymeleaf.org" lang="en" xmlns:shiro="http://www.thymeleaf.org/extras/thymeleaf-extras-shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> <p th:text="${msg}"> <!-- 经测试:超链接上方不加<hr>就无法显示超链接,加上<hr>后俩超链接能正常显示 --> <hr> <div th:if="${session.loginUser==null}"> <a th:href="@{/toLoginPage}">登录</a> </div> <div shiro:hasPermission="user:add"> <a th:href="@{/user/add}">add</a> </div> <div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">update</a> </div> </body> </html>
浙公网安备 33010602011771号