Spring Boot整合Spring Security简单实现登入登出从零搭建教程
前言
spring security是一个能够为基于spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在spring应用上下文中配置的bean,充分利用了spring ioc,di(控制反转inversion of control ,di:dependency injection 依赖注入)和aop(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
本文主要给大家介绍了关于spring boot整合spring security实现登入登出的相关内容,下面话不多说了,来一起看看详细的介绍吧
技术栈 : springboot + springsecurity + jpa + freemark ,完整项目地址 : https://github.com/ealenxie/spring-security-login ()
方法如下:
1 . 新建一个spring-security-login的maven项目 ,pom.xml添加基本依赖 :
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <groupid>com.wuxicloud</groupid> <artifactid>spring-security-login</artifactid> <version>1.0</version> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version>1.5.6.release</version> </parent> <properties> <author>ealenxie</author> <description>springboot整合springsecurity实现简单登入登出</description> </properties> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-jpa</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-security</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-freemarker</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-aop</artifactid> </dependency> <!--alibaba--> <dependency> <groupid>com.alibaba</groupid> <artifactid>druid</artifactid> <version>1.0.24</version> </dependency> <dependency> <groupid>com.alibaba</groupid> <artifactid>fastjson</artifactid> <version>1.2.31</version> </dependency> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <scope>runtime</scope> </dependency> </dependencies> </project>
2 . 准备你的数据库,设计表结构,要用户使用登入登出,新建用户表。
drop table if exists `user`; create table `user` ( `id` int(11) not null auto_increment, `user_uuid` varchar(70) character set utf8 collate utf8_general_ci default null, `username` varchar(255) character set utf8 collate utf8_general_ci default null, `password` varchar(255) character set utf8 collate utf8_general_ci default null, `email` varchar(255) character set utf8 collate utf8_general_ci default null, `telephone` varchar(255) character set utf8 collate utf8_general_ci default null, `role` int(10) default null, `image` varchar(255) character set utf8 collate utf8_general_ci default null, `last_ip` varchar(255) character set utf8 collate utf8_general_ci default null, `last_time` varchar(255) character set utf8 collate utf8_general_ci default null, primary key (`id`) using btree ) engine = innodb auto_increment = 2 character set = utf8 collate = utf8_general_ci row_format = compact; set foreign_key_checks = 1;
3 . 用户对象user.java :
import javax.persistence.*; /** * created by ealenxie on 2018/7/5 15:17 */ @entity @table(name = "user") public class user { @id @generatedvalue(strategy = generationtype.auto) private integer id; private string user_uuid; //用户uuid private string username; //用户名 private string password; //用户密码 private string email; //用户邮箱 private string telephone; //电话号码 private string role; //用户角色 private string image; //用户头像 private string last_ip; //上次登录ip private string last_time; //上次登录时间 public integer getid() { return id; } public string getrole() { return role; } public void setrole(string role) { this.role = role; } public string getimage() { return image; } public void setimage(string image) { this.image = image; } public void setid(integer id) { this.id = id; } public string getusername() { return username; } public void setusername(string username) { this.username = username; } public string getemail() { return email; } public void setemail(string email) { this.email = email; } public string gettelephone() { return telephone; } public void settelephone(string telephone) { this.telephone = telephone; } public string getpassword() { return password; } public void setpassword(string password) { this.password = password; } public string getuser_uuid() { return user_uuid; } public void setuser_uuid(string user_uuid) { this.user_uuid = user_uuid; } public string getlast_ip() { return last_ip; } public void setlast_ip(string last_ip) { this.last_ip = last_ip; } public string getlast_time() { return last_time; } public void setlast_time(string last_time) { this.last_time = last_time; } @override public string tostring() { return "user{" + "id=" + id + ", user_uuid='" + user_uuid + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + ", telephone='" + telephone + '\'' + ", role='" + role + '\'' + ", image='" + image + '\'' + ", last_ip='" + last_ip + '\'' + ", last_time='" + last_time + '\'' + '}'; } }
4 . application.yml配置一些基本属性
spring: resources: static-locations: classpath:/ freemarker: template-loader-path: classpath:/templates/ suffix: .html content-type: text/html charset: utf-8 datasource: url: jdbc:mysql://localhost:3306/yourdatabase username: yourname password: yourpass driver-class-name: com.mysql.jdbc.driver type: com.alibaba.druid.pool.druiddatasource server: port: 8083 error: whitelabel: enabled: true
5 . 考虑我们应用的效率 , 可以配置数据源和线程池 :
package com.wuxicloud.config; import com.alibaba.druid.pool.druiddatasource; import com.alibaba.druid.pool.druiddatasourcefactory; import com.alibaba.druid.support.http.statviewservlet; import com.alibaba.druid.support.http.webstatfilter; import org.springframework.beans.factory.annotation.autowired; import org.springframework.boot.context.properties.configurationproperties; import org.springframework.boot.web.servlet.filterregistrationbean; import org.springframework.boot.web.servlet.servletregistrationbean; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.core.env.*; import javax.sql.datasource; import java.util.hashmap; import java.util.map; import java.util.properties; @configuration public class druidconfig { private static final string db_prefix = "spring.datasource."; @autowired private environment environment; @bean @configurationproperties(prefix = db_prefix) public datasource druiddatasource() { properties dbproperties = new properties(); map<string, object> map = new hashmap<>(); for (propertysource<?> propertysource : ((abstractenvironment) environment).getpropertysources()) { getpropertiesfromsource(propertysource, map); } dbproperties.putall(map); druiddatasource dds; try { dds = (druiddatasource) druiddatasourcefactory.createdatasource(dbproperties); dds.init(); } catch (exception e) { throw new runtimeexception("load datasource error, dbproperties is :" + dbproperties, e); } return dds; } private void getpropertiesfromsource(propertysource<?> propertysource, map<string, object> map) { if (propertysource instanceof mappropertysource) { for (string key : ((mappropertysource) propertysource).getpropertynames()) { if (key.startswith(db_prefix)) map.put(key.replacefirst(db_prefix, ""), propertysource.getproperty(key)); else if (key.startswith(db_prefix)) map.put(key.replacefirst(db_prefix, ""), propertysource.getproperty(key)); } } if (propertysource instanceof compositepropertysource) { for (propertysource<?> s : ((compositepropertysource) propertysource).getpropertysources()) { getpropertiesfromsource(s, map); } } } @bean public servletregistrationbean druidservlet() { return new servletregistrationbean(new statviewservlet(), "/druid/*"); } @bean public filterregistrationbean filterregistrationbean() { filterregistrationbean filterregistrationbean = new filterregistrationbean(); filterregistrationbean.setfilter(new webstatfilter()); filterregistrationbean.addurlpatterns("/*"); filterregistrationbean.addinitparameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); return filterregistrationbean; } }
配置线程池 :
package com.wuxicloud.config; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.scheduling.annotation.enableasync; import org.springframework.scheduling.concurrent.threadpooltaskexecutor; import java.util.concurrent.executor; import java.util.concurrent.threadpoolexecutor; @configuration @enableasync public class threadpoolconfig { @bean public executor getexecutor() { threadpooltaskexecutor executor = new threadpooltaskexecutor(); executor.setcorepoolsize(5);//线程池维护线程的最少数量 executor.setmaxpoolsize(30);//线程池维护线程的最大数量 executor.setqueuecapacity(8); //缓存队列 executor.setrejectedexecutionhandler(new threadpoolexecutor.callerrunspolicy()); //对拒绝task的处理策略 executor.setkeepaliveseconds(60);//允许的空闲时间 executor.initialize(); return executor; } }
6.用户需要根据用户名进行登录,访问数据库 :
import com.wuxicloud.model.user; import org.springframework.data.jpa.repository.jparepository; /** * created by ealenxie on 2018/7/11 14:23 */ public interface userrepository extends jparepository<user, integer> { user findbyusername(string username); }
7.构建真正用于springsecurity登录的安全用户(userdetails),我这里使用新建了一个pojo来实现 :
package com.wuxicloud.security; import com.wuxicloud.model.user; import org.springframework.security.core.grantedauthority; import org.springframework.security.core.authority.simplegrantedauthority; import org.springframework.security.core.userdetails.userdetails; import java.util.arraylist; import java.util.collection; public class securityuser extends user implements userdetails { private static final long serialversionuid = 1l; public securityuser(user user) { if (user != null) { this.setuser_uuid(user.getuser_uuid()); this.setusername(user.getusername()); this.setpassword(user.getpassword()); this.setemail(user.getemail()); this.settelephone(user.gettelephone()); this.setrole(user.getrole()); this.setimage(user.getimage()); this.setlast_ip(user.getlast_ip()); this.setlast_time(user.getlast_time()); } } @override public collection<? extends grantedauthority> getauthorities() { collection<grantedauthority> authorities = new arraylist<>(); string username = this.getusername(); if (username != null) { simplegrantedauthority authority = new simplegrantedauthority(username); authorities.add(authority); } return authorities; } @override public boolean isaccountnonexpired() { return true; } @override public boolean isaccountnonlocked() { return true; } @override public boolean iscredentialsnonexpired() { return true; } @override public boolean isenabled() { return true; } }
8 . 核心配置,配置springsecurity访问策略,包括登录处理,登出处理,资源访问,密码基本加密。
package com.wuxicloud.config; import com.wuxicloud.dao.userrepository; import com.wuxicloud.model.user; import com.wuxicloud.security.securityuser; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.beans.factory.annotation.autowired; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; 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.configuration.enablewebsecurity; import org.springframework.security.config.annotation.web.configuration.websecurityconfigureradapter; import org.springframework.security.core.authentication; import org.springframework.security.core.userdetails.userdetails; import org.springframework.security.core.userdetails.userdetailsservice; import org.springframework.security.core.userdetails.usernamenotfoundexception; import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder; import org.springframework.security.web.authentication.savedrequestawareauthenticationsuccesshandler; import org.springframework.security.web.authentication.logout.logoutsuccesshandler; import javax.servlet.servletexception; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import java.io.ioexception; /** * created by ealenxie on 2018/1/11. */ @configuration @enablewebsecurity public class websecurityconfig extends websecurityconfigureradapter { private static final logger logger = loggerfactory.getlogger(websecurityconfig.class); @override protected void configure(httpsecurity http) throws exception { //配置策略 http.csrf().disable(); http.authorizerequests(). antmatchers("/static/**").permitall().anyrequest().authenticated(). and().formlogin().loginpage("/login").permitall().successhandler(loginsuccesshandler()). and().logout().permitall().invalidatehttpsession(true). deletecookies("jsessionid").logoutsuccesshandler(logoutsuccesshandler()). and().sessionmanagement().maximumsessions(10).expiredurl("/login"); } @autowired public void configureglobal(authenticationmanagerbuilder auth) throws exception { auth.userdetailsservice(userdetailsservice()).passwordencoder(passwordencoder()); auth.erasecredentials(false); } @bean public bcryptpasswordencoder passwordencoder() { //密码加密 return new bcryptpasswordencoder(4); } @bean public logoutsuccesshandler logoutsuccesshandler() { //登出处理 return new logoutsuccesshandler() { @override public void onlogoutsuccess(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, authentication authentication) throws ioexception, servletexception { try { securityuser user = (securityuser) authentication.getprincipal(); logger.info("user : " + user.getusername() + " logout success ! "); } catch (exception e) { logger.info("logout exception , e : " + e.getmessage()); } httpservletresponse.sendredirect("/login"); } }; } @bean public savedrequestawareauthenticationsuccesshandler loginsuccesshandler() { //登入处理 return new savedrequestawareauthenticationsuccesshandler() { @override public void onauthenticationsuccess(httpservletrequest request, httpservletresponse response, authentication authentication) throws ioexception, servletexception { user userdetails = (user) authentication.getprincipal(); logger.info("user : " + userdetails.getusername() + " login success ! "); super.onauthenticationsuccess(request, response, authentication); } }; } @bean public userdetailsservice userdetailsservice() { //用户登录实现 return new userdetailsservice() { @autowired private userrepository userrepository; @override public userdetails loaduserbyusername(string s) throws usernamenotfoundexception { user user = userrepository.findbyusername(s); if (user == null) throw new usernamenotfoundexception("username " + s + " not found"); return new securityuser(user); } }; } }
9.至此,已经基本将配置搭建好了,从上面核心可以看出,配置的登录页的url 为/login,可以创建基本的controller来验证登录了。
package com.wuxicloud.web; import com.wuxicloud.model.user; import org.springframework.security.core.authentication; import org.springframework.security.core.context.securitycontext; import org.springframework.security.core.context.securitycontextholder; import org.springframework.security.core.userdetails.userdetails; import org.springframework.stereotype.controller; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.requestmethod; import org.springframework.web.context.request.requestcontextholder; import org.springframework.web.context.request.servletrequestattributes; import javax.servlet.http.httpservletrequest; /** * created by ealenxie on 2018/1/11. */ @controller public class logincontroller { @requestmapping(value = "/login", method = requestmethod.get) public string login() { return "login"; } @requestmapping("/") public string root() { return "index"; } public user getuser() { //为了session从获取用户信息,可以配置如下 user user = new user(); securitycontext ctx = securitycontextholder.getcontext(); authentication auth = ctx.getauthentication(); if (auth.getprincipal() instanceof userdetails) user = (user) auth.getprincipal(); return user; } public httpservletrequest getrequest() { return ((servletrequestattributes) requestcontextholder.getrequestattributes()).getrequest(); } }
11 . springboot基本的启动类 application.class
package com.wuxicloud; import org.springframework.boot.springapplication; import org.springframework.boot.autoconfigure.springbootapplication; /** * created by ealenxie on 2018/7/11 15:01 */ @springbootapplication public class application { public static void main(string[] args) { springapplication.run(application.class, args); } }
11.根据freemark和controller里面可看出配置的视图为 /templates/index.html和/templates/index.login。所以创建基本的登录页面和登录成功页面。
login.html
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>用户登录</title> </head> <body> <form action="/login" method="post"> 用户名 : <input type="text" name="username"/> 密码 : <input type="password" name="password"/> <input type="submit" value="登录"> </form> </body> </html>
注意 : 这里方法必须是post,因为get在controller被重写了,用户名的name属性必须是username,密码的name属性必须是password
index.html
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>首页</title> <#assign user=session.spring_security_context.authentication.principal/> </head> <body> 欢迎你,${user.username}<br/> <a href="/logout">注销</a> </body> </html>
注意 : 为了从session中获取到登录的用户信息,根据配置springsecurity的用户信息会放在session.spring_security_context.authentication.principal里面,根据freemarker模板引擎的特点,可以通过这种方式进行获取 : <#assign user=session.spring_security_context.authentication.principal/>
12 . 为了方便测试,我们在数据库中插入一条记录,注意,从websecurity.java配置可以知道密码会被加密,所以我们插入的用户密码应该是被加密的。
这里假如我们使用的密码为admin,则加密过后的字符串是 $2a$04$1oiua3yechbxqbji8jamyukznlwzwvfeqjkahnwaeqwnacjt6ukqu
测试类如下 :
package com.wuxicloud.security; import org.junit.test; import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder; /** * created by ealenxie on 2018/7/11 15:13 */ public class testencoder { @test public void encoder() { string password = "admin"; bcryptpasswordencoder encoder = new bcryptpasswordencoder(4); string enpassword = encoder.encode(password); system.out.println(enpassword); } }
测试登录,从上面的加密的密码我们插入一条数据到数据库中。
insert into `user` values (1, 'd242ae49-4734-411e-8c8d-d2b09e87c3c8', 'ealenxie', '$2a$04$petexpgclkfdln4tyfxk0u8ryazmzdhlaswlx/xxm8hgqar1c892w', 'sssss', 'ssssssssss', 1, 'g', '0:0:0:0:0:0:0:1', '2018-07-11 11:26:27');
13 . 启动项目进行测试 ,访问 localhost:8083
点击登录,登录失败会留在当前页面重新登录,成功则进入index.html
登录如果成功,可以看到后台打印登录成功的日志 :
页面进入index.html :
点击注销 ,则回重新跳转到login.html,后台也会打印登出成功的日志 :
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。