学习项目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>

 

posted on 2025-02-04 17:50  平凡力量  阅读(23)  评论(0)    收藏  举报