Spring Security使用

Spring Security

在web应用开发中,安全无疑是十分重要的,选择Spring Security来保护web应用是一个非常好的选择。

Spring Security 是spring项目之中的一个安全模块,可以非常方便与spring项目无缝集成。特别是在spring boot项目中加入spring security更是十分简单。本篇我们介绍spring security,以及spring security在web应用中的使用。

一个例子入门

假设我们现在创建好了一个springboot的web应用,有一个控制器如下:

@Controller
public class AppController {@RequestMapping("/hello")@ResponseBodyString home() {return "Hello ,spring security!";}
}

我们启动应用,假设端口是8080,那么当我们在浏览器访问http://localhost:8080/hello的时候可以在浏览器看到Hello ,spring security!。

加入spring security 保护应用

此时,/hello是可以自由访问。假设,我们需要具有某个角色的用户才能访问的时候,我们可以引入spring security来进行保护。加入如下maven依赖,并重启应用:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

再次访问/hello,我们可以得到一个http-basic的认证弹窗,如下:

 

代码如下:

<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table><tr><td>User:</td><td><input type='text' name='username' value=''></td></tr><tr><td>Password:</td><td><input type='password' name='password'/></td></tr><tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr><input name="_csrf" type="hidden" value="635780a5-6853-4fcd-ba14-77db85dbd8bd" />
</table>
</form></body></html>

我们可以发现,这里有个form 。action=”/login”,这个/login是spring security提供的。form表单提交了三个数据:

username 用户名   password 密码   _csrf CSRF保护方面的内容

说明spring security 已经起作用了。这时,它为你生成了账号和密码,在内存中。

但是,我们不可能只是这么使用它,我们如何通过访问数据库来登录,验证权限呢?

接下来通过一个实例继续深入。

demo项目权限介绍

我们通过一个很简单的项目来认识一下Spring Security。

index.html:社区首页(只有四个链接)----------任何人都可以访问

discuss.html:帖子详情页面(只有一句话)------任何人都可以访问

letter.html:私信列表(只有一句话)-------只有登陆后的用户才能访问

admin.html:管理员页面(只有一句话)----只有管理员才能访问

login.html:登陆页面(有表单)----------------不符合要求时,可以登录

我们的目的,就是把这些权限管理起来。

 

静态页面代码展示

 

下面展示一下这些页面的代码:

index.html:社区首页(只有四个链接)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>首页</title>
</head>
<body><h1>社区首页</h1><ul><li><a th:href="@{/discuss}">帖子详情</a></li><li><a th:href="@{/letter}">私信列表</a></li><li><a th:href="@{/loginpage}">登录</a></li><li><a th:href="@{/loginpage}">退出</a></li></ul>
</body>
</html>

discuss.html:帖子详情页面(只有一句话)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>帖子</title>
</head>
<body><h1>帖子详情页面</h1>
</body>
</html>

letter.html:私信列表(只有一句话)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>私信</title>
</head>
<body><h1>私信列表页面</h1>
</body>
</html>

admin.html:管理员页面(只有一句话)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>管理员</title>
</head>
<body><h1>管理员专属页面</h1>
</body>
</html>

login.html:登陆页面(有表单)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>登录</title>
</head>
<body><h1>登录社区</h1><form method="post" action="#"><p style="color:red;"><!--提示信息--></p><p>账号:<input type="text" ></p><p>密码:<input type="password" ></p><p>验证码:<input type="text" ></p><p><input type="submit" value="登录"></p></form></body>
</html>

service层和user的操作

首先要处理我们的用户user表,写出获取权限的方法。

实现UserDetails 接口,和接口定义的方法。

方法的作用我都已经标注在代码里。

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;public class User implements UserDetails {private int id;private String username;private String password;private String salt;private String email;private int type;private int status;private String activationCode;private String headerUrl;private Date createTime;
/*
get/set方法
toSring方法
*/// true: 账号未过期.@Overridepublic boolean isAccountNonExpired() {return true;}// true: 账号未锁定.@Overridepublic boolean isAccountNonLocked() {return true;}// true: 凭证未过期.@Overridepublic boolean isCredentialsNonExpired() {return true;}// true: 账号可用.@Overridepublic boolean isEnabled() {return true;}// 返回用户权限//我们有两种用户:1代表管理员,2代表普通用户@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> list = new ArrayList<>();list.add(new GrantedAuthority() {@Overridepublic String getAuthority() {switch (type) {case 1:return "ADMIN";default:return "USER";}}});return list;}}

着重介绍一下getAuthorities方法:

它的返回值是一个权限集合,因为我们真实开发可能是这样的:

用户表:记录了用户类型(是普通用户还是1级管理员、2级管理员、卖家买家等等)

权限表:记录了每个角色有什么权限,比如普通用户可以发帖评论点赞,管理员可以删贴置顶等等。

最后我们通过用户类型,查到多种权限,并且返回。

因为每种用户有多种权限,所以getAuthorities方法的返回值是一个权限集合。,这个集合可以装很多GrantedAuthority对象。

本代码的集合只装了一个权限对象,并且重写了对应回去权限的方法。。


service层实现接口和对应方法。

import com.nowcoder.community.dao.UserMapper;
import com.nowcoder.community.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;@Service
public class UserService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;public User findUserByName(String username) {return userMapper.selectByName(username);}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return this.findUserByName(username);}
}

这个接口是要实现查找对应的用户。

我们自己写登录逻辑的时候,一样要这么做:用账号(id)查到用户,读取密码,看看用户输入的和在数据库查到的是否相同,

security底层也是做了类似的事情,所以我们需要告诉他如何查找用户。

如果我们之前写过selectByName之类的方法,可以直接调用即可。

这里我把dao层和xml实现也给出来。

import com.nowcoder.community.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {User selectByName(String username);
}
<mapper namespace="com.community.dao.UserMapper"><sql id="selectFields">id, username, password, salt, email, type, status, activation_code, header_url, create_time</sql><select id="selectByName" resultType="User">select<include refid="selectFields"></include>from userwhere username = #{username}</select></mapper>

核心操作

书写配置类统一管理。

有详细的注释。

通常我们需要书写如下代码:

忽略哪些资源?

认证

授权

package com.community.config;import com.community.entity.User;
import com.community.service.UserService;
import com.community.util.CommunityUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@Overridepublic void configure(WebSecurity web) throws Exception {// 忽略静态资源的访问web.ignoring().antMatchers("/resources/**");}// AuthenticationManager: 认证的核心接口.// AuthenticationManagerBuilder: 用于构建AuthenticationManager对象的工具.// ProviderManager: AuthenticationManager接口的默认实现类.@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 内置的认证规则// auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345"));// 自定义认证规则// AuthenticationProvider: ProviderManager持有一组AuthenticationProvider,每个AuthenticationProvider负责一种认证.// 委托模式: ProviderManager将认证委托给AuthenticationProvider.auth.authenticationProvider(new AuthenticationProvider() {// Authentication: 用于封装认证信息的接口,不同的实现类代表不同类型的认证信息.@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String username = authentication.getName();String password = (String) authentication.getCredentials();User user = userService.findUserByName(username);if (user == null) {throw new UsernameNotFoundException("账号不存在!");}password = CommunityUtil.md5(password + user.getSalt());if (!user.getPassword().equals(password)) {throw new BadCredentialsException("密码不正确!");}// principal: 主要信息; credentials: 证书; authorities: 权限;return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());}// 当前的AuthenticationProvider支持哪种类型的认证.@Overridepublic boolean supports(Class<?> aClass) {// UsernamePasswordAuthenticationToken: Authentication接口的常用的实现类.return UsernamePasswordAuthenticationToken.class.equals(aClass);}});}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 登录相关配置http.formLogin().loginPage("/loginpage").loginProcessingUrl("/login").successHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.sendRedirect(request.getContextPath() + "/index");}}).failureHandler(new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {request.setAttribute("error", e.getMessage());request.getRequestDispatcher("/loginpage").forward(request, response);}});// 退出相关配置http.logout().logoutUrl("/logout").logoutSuccessHandler(new LogoutSuccessHandler() {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.sendRedirect(request.getContextPath() + "/index");}});// 授权配置http.authorizeRequests().antMatchers("/letter").hasAnyAuthority("USER", "ADMIN").antMatchers("/admin").hasAnyAuthority("ADMIN").and().exceptionHandling().accessDeniedPage("/denied");// 增加Filter,处理验证码http.addFilterBefore(new Filter() {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;if (request.getServletPath().equals("/login")) {String verifyCode = request.getParameter("verifyCode");if (verifyCode == null || !verifyCode.equalsIgnoreCase("1234")) {request.setAttribute("error", "验证码错误!");request.getRequestDispatcher("/loginpage").forward(request, response);return;}}// 让请求继续向下执行.filterChain.doFilter(request, response);}}, UsernamePasswordAuthenticationFilter.class);// 记住我http.rememberMe().tokenRepository(new InMemoryTokenRepositoryImpl()).tokenValiditySeconds(3600 * 24).userDetailsService(userService);}
}

表单

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>登录</title>
</head>
<body><h1>登录社区</h1><form method="post" th:action="@{/login}"><p style="color:red;" th:text="${error}"><!--提示信息--></p><p>账号:<input type="text" name="username" th:value="${param.username}"></p><p>密码:<input type="password" name="password" th:value="${param.password}"></p><p>验证码:<input type="text" name="verifyCode"> <i>1234</i></p><p><input type="checkbox" name="remember-me"> 记住我</p><p><input type="submit" value="登录"></p></form></body>
</html>

HomeController 

package com.nowcoder.community.controller;import com.nowcoder.community.entity.User;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;@Controller
public class HomeController {@RequestMapping(path = "/index", method = RequestMethod.GET)public String getIndexPage(Model model) {// 认证成功后,结果会通过SecurityContextHolder存入SecurityContext中.Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();if (obj instanceof User) {model.addAttribute("loginUser", obj);}return "/index";}@RequestMapping(path = "/discuss", method = RequestMethod.GET)public String getDiscussPage() {return "/site/discuss";}@RequestMapping(path = "/letter", method = RequestMethod.GET)public String getLetterPage() {return "/site/letter";}@RequestMapping(path = "/admin", method = RequestMethod.GET)public String getAdminPage() {return "/site/admin";}@RequestMapping(path = "/loginpage", method = {RequestMethod.GET, RequestMethod.POST})public String getLoginPage() {return "/site/login";}// 拒绝访问时的提示页面@RequestMapping(path = "/denied", method = RequestMethod.GET)public String getDeniedPage() {return "/error/404";}}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/444917.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nginx python webpy 配置安装

安装webpy$ wget http://webpy.org/static/web.py-0.34.tar.gz$ tar xvzf web.py-0.34.tar.gz$ cd web.py-0.34$ sudo python setup.py install安装 Fluphttp://www.saddi.com/software/flup/dist/flup-1.0.2.tar.gz$ wget http://www.saddi.com/software/flup/dist/flup-1.0.2…

PaperNotes(7)-GANs模式坍塌/训练不稳定

GANs-模式坍塌-训练不稳定1.训练不稳定问题相关文章1.1 DCGAN1.2Big-GAN1.3WGAN 、WGAN-GP、SN-WGAN1.4其他工作2.模式坍塌问题相关文章2.1 MAD-GAN2.2 Unrolled GAN2.3 DRAGAN2.4 D2GAN2.5 InfoGAN2.6 Deligan2.7 EBGAN2.8 Maximum Entropy Generators for Energy-Based Model…

thinkphp框架起步认识

先看看thinkphp的文档吧&#xff1a;这是我在网上找的一个不错的链接地址&#xff0c;对自己有用&#xff0c;同时相信对读者也有用吧。 http://doc.thinkphp.cn/manual/class.html ThinkPHP 跨模块调用操作方法&#xff08;A方法与R方法&#xff09; 跨模块调用操作方法 前面说…

leetcode403 青蛙过河

一只青蛙想要过河。 假定河流被等分为 x 个单元格&#xff0c;并且在每一个单元格内都有可能放有一石子&#xff08;也有可能没有&#xff09;。 青蛙可以跳上石头&#xff0c;但是不可以跳入水中。 给定石子的位置列表&#xff08;用单元格序号升序表示&#xff09;&#xff…

PaperNotes(8)-Stein Variational Gradient Descent A General Purpose Bayesian Inference Algorithm

通用贝叶斯推理算法-Stein Variational Gradient DescentAbstract1 Introduction2 Background3 Variational Inference Using Smooth Transforms3.1 Stein Operator as the Derivative of KL Divergence定理3.1引理3.23.2 Stein Variational Gradient Descent4 Related Works5 …

thinkphp的增删改查

ThinkPHP 添加数据 add 方法 ThinkPHP 内置的 add 方法用于向数据表添加数据&#xff0c;相当于 SQL 中的 INSERT INTO 行为。ThinkPHP Insert 添加数据添加数据 add 方法是 CURD&#xff08;Create,Update,Read,Delete / 创建,修改,读取,删除&#xff09;中的 Create 的实现&a…

leetcode115 不同的子序列

给定一个字符串 S 和一个字符串 T&#xff0c;计算在 S 的子序列中 T 出现的个数。 一个字符串的一个子序列是指&#xff0c;通过删除一些&#xff08;也可以不删除&#xff09;字符且不干扰剩余字符相对位置所组成的新字符串。&#xff08;例如&#xff0c;"ACE" 是…

ThinkPHP 模板循环输出 Volist 标签

volist 标签用于在模板中循环输出数据集或者多维数组。volist 标签在模块操作中&#xff0c;select() 方法返回的是一个二维数组&#xff0c;可以用 volist 直接输出&#xff1a;<volist name"list" id"vo"> 用 户 名&#xff1a;{$vo[username]}&l…

MachineLearning(9)-最大似然、最小KL散度、交叉熵损失函数三者的关系

最大似然-最小KL散度-最小化交叉熵损失-三者的关系问题缘起&#xff1a;给定一组数据(x1,x2,...,xm)(x^1,x^2,...,x^m)(x1,x2,...,xm),希望找到这组数据服从的分布。此种情况下&#xff0c;分布规律用概率密度p(x)表征。 问题归处&#xff1a;如果能够建模/近似建模p(x)&#…

ThinkPHP redirect 页面重定向使用详解与实例

ThinkPHP redirect 方法ThinkPHP redirect 方法可以实现页面的重定向&#xff08;跳转&#xff09;功能。redirect 方法语法如下&#xff1a;$this->redirect(string url, array params, int delay, string msg) 参数说明&#xff1a;url 必须&#xff0c;重定向的 URL 表达…

PaperNotes(9)-Learning deep energy model: contrastive divergence vs. Amortized MLE

Learning deep energy model: contrastive divergence vs. Amortized MLEabstract1 Introduction2 Background2.1 stein variational gradient descent2.2 learning energy model**contrastive Divergence**abstract 受SVGD算法的启发,本文提出两个算法用于从数据中学习深度能…

windows下的gvim配置

首要任务是下载安装Gvim7.3 。 安装完后&#xff0c;gvim菜单中文出现乱码&#xff0c;在_vimrcset文件中增加&#xff1a; " 配置多语言环境,解决中文乱码问题 if has("multi_byte") " UTF-8 编码 set encodingutf-8 set termencodingutf…

leetcode104 二叉树的最大深度

给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 示例&#xff1a; 给定二叉树 [3,9,20,null,null,15,7]&#xff0c; 3 / \ 9 20 / \ 15 7 返回它的最大深度…

C++的安全类型转换的讨论

关于强制类型转换的问题,很多书都讨论过,写的最详细的是C++ 之父的《C++的设计和演化》。最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_cast, dynamic_cast。标准C++中有四个类型转换符:static_cast、dynamic_cast、reinterpret_ca…

PaperNotes(10)-Maximum Entropy Generators for Energy-Based Models

Maximum Entropy Generators for Energy-Based ModelsAbstract1 Introduction2 Background3 Maximum Entropy Generators for Energy-Based Models4 Experiments5 Related Work6 Conclusion7 AcknowledgementsAbstract 由于对数似然梯度的难以计算&#xff0c;能量模型的最大似…

leetcode105 前序中序遍历序列构造二叉树

根据一棵树的前序遍历与中序遍历构造二叉树。 注意: 你可以假设树中没有重复的元素。 例如&#xff0c;给出 前序遍历 preorder [3,9,20,15,7] 中序遍历 inorder [9,3,15,20,7] 返回如下的二叉树&#xff1a; 3 / \ 9 20 / \ 15 7 思路&#xff1a; 1、…

c++的虚拟继承 的一些思考吧

虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class …

对于linux socket与epoll配合相关的一些心得记录

对于linux socket与epoll配合相关的一些心得记录 没有多少高深的东西&#xff0c;全当记录&#xff0c;虽然简单&#xff0c;但是没有做过测试还是挺容易让人糊涂的int nRecvBuf32*1024;//设置为32Ksetsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int))…

leetcode144 二叉树的前序遍历

给定一个二叉树&#xff0c;返回它的 前序 遍历。 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [1,2,3] 进阶: 递归算法很简单&#xff0c;你可以通过迭代算法完成吗&#xff1f; 思路&#xff1a;模仿递归的思路压栈即可。 /*** Definition for a bi…

AJAX大总结

1、AJAX概述 1.1 什么是AJAX AJAX&#xff08;Asynchronous Javascript And XML&#xff09;翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互&#xff0c;传输的数据为XML&#xff08;当然&#xff0c;传输的数据不只是XML&#xff09;。 …