change input apache-fop
This commit is contained in:
parent
eb495127b2
commit
df762d4ce6
|
@ -26,20 +26,20 @@ import org.w3c.dom.Document;
|
|||
import org.w3c.tidy.Tidy;
|
||||
|
||||
public class ApacheFOPConvertHTMLTest {
|
||||
private String inputFile = "src/test/resources/hello.html";
|
||||
private String inputFile = "src/test/resources/input.html";
|
||||
private String style = "src/test/resources/xhtml2fo.xsl";
|
||||
private String style1 = "src/test/resources/docbook-xsl/fo/docbook.xsl";
|
||||
private String output = "src/test/resources/hello.pdf";
|
||||
private String output1 = "src/test/resources/hello1.pdf";
|
||||
private String output2 = "src/test/resources/hello2.pdf";
|
||||
private String foFile = "src/test/resources/hello.fo";
|
||||
private String xmlFile = "src/test/resources/hello.xml";
|
||||
private String output_jtidy = "src/test/resources/output_jtidy.pdf";
|
||||
private String output_html2fo = "src/test/resources/output_html2fo.pdf";
|
||||
private String output_herold = "src/test/resources/output_herold.pdf";
|
||||
private String foFile = "src/test/resources/input.fo";
|
||||
private String xmlFile = "src/test/resources/input.xml";
|
||||
|
||||
@Test
|
||||
public void whenTransformHTMLToPDFUsingJTidy_thenCorrect() throws Exception {
|
||||
final Document xhtml = fromHTMLToXHTML();
|
||||
final Document fo = fromXHTMLToFO(xhtml);
|
||||
fromFODocumentToPDF(fo, output);
|
||||
fromFODocumentToPDF(fo, output_jtidy);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -51,7 +51,7 @@ public class ApacheFOPConvertHTMLTest {
|
|||
public void whenTransformFromHeroldToPDF_thenCorrect() throws Exception {
|
||||
fromHTMLTOXMLUsingHerold();
|
||||
final Document fo = fromXMLFileToFO();
|
||||
fromFODocumentToPDF(fo, output2);
|
||||
fromFODocumentToPDF(fo, output_herold);
|
||||
}
|
||||
|
||||
private Document fromHTMLToXHTML() throws FileNotFoundException {
|
||||
|
@ -92,7 +92,7 @@ public class ApacheFOPConvertHTMLTest {
|
|||
|
||||
private void fromFOFileToPDF() throws Exception {
|
||||
final FopFactory fopFactory = FopFactory.newInstance();
|
||||
final OutputStream outStream = new BufferedOutputStream(new FileOutputStream(new File(output1)));
|
||||
final OutputStream outStream = new BufferedOutputStream(new FileOutputStream(new File(output_html2fo)));
|
||||
final Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, outStream);
|
||||
|
||||
final TransformerFactory factory = TransformerFactory.newInstance();
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:fox="http://xml.apache.org/fop/extensions">
|
||||
<!-- Creator="html2fo" Version="0.4.2" -->
|
||||
<fo:layout-master-set>
|
||||
<fo:simple-page-master margin-right="2.0cm" margin-left="2.0cm" margin-bottom="1.0cm" margin-top="1.0cm" page-width="21cm" page-height="29.7cm" master-name="first">
|
||||
<fo:region-body margin-bottom="1.5cm" margin-top="1.5cm"/>
|
||||
<fo:region-before extent="1.5cm"/>
|
||||
<fo:region-after extent="1.0cm"/>
|
||||
</fo:simple-page-master>
|
||||
</fo:layout-master-set>
|
||||
<fo:page-sequence master-reference="first" language="en" hyphenate="true">
|
||||
|
||||
|
||||
|
||||
<fo:static-content flow-name="xsl-region-before">
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Hello World</fo:block></fo:static-content>
|
||||
|
||||
<fo:static-content flow-name="xsl-region-after">
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">
|
||||
|
||||
... the footer should be inserted here ...
|
||||
|
||||
</fo:block></fo:static-content>
|
||||
|
||||
<fo:flow flow-name="xsl-region-body">
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="32pt" font-size="16pt">My name is John Doe</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"> Hello World!</fo:block>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" id="LastPage" line-height="1pt" font-size="1pt"></fo:block></fo:flow>
|
||||
</fo:page-sequence></fo:root>
|
||||
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Hello World</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>My name is John Doe</h1>
|
||||
<p> Hello World!</p>
|
||||
</body>
|
||||
</html>
|
||||
|
Binary file not shown.
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<article version="5.0" xml:lang="en" xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink">
|
||||
<info>
|
||||
<title>Hello World</title>
|
||||
</info>
|
||||
<section>
|
||||
<title>My name is John Doe</title>
|
||||
<para> Hello World!</para>
|
||||
</section>
|
||||
</article>
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,479 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:fox="http://xml.apache.org/fop/extensions">
|
||||
<!-- Creator="html2fo" Version="0.4.2" -->
|
||||
<fo:layout-master-set>
|
||||
<fo:simple-page-master margin-right="2.0cm" margin-left="2.0cm" margin-bottom="1.0cm" margin-top="1.0cm" page-width="21cm" page-height="29.7cm" master-name="first">
|
||||
|
||||
<fo:region-body margin-bottom="1.5cm" margin-top="1.5cm"/>
|
||||
<fo:region-before extent="1.5cm"/>
|
||||
<fo:region-after extent="1.0cm"/>
|
||||
</fo:simple-page-master>
|
||||
</fo:layout-master-set>
|
||||
<fo:page-sequence master-reference="first" language="en-US" hyphenate="true">
|
||||
|
||||
<fo:static-content flow-name="xsl-region-before">
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Registration &#8211; Activate a New Account by Email | Technical Articles</fo:block></fo:static-content>
|
||||
|
||||
<fo:static-content flow-name="xsl-region-after">
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">
|
||||
|
||||
... the footer should be inserted here ...
|
||||
|
||||
</fo:block></fo:static-content>
|
||||
|
||||
<fo:flow flow-name="xsl-region-body"><fo:block line-height="12pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">
|
||||
|
||||
|
||||
|
||||
|
||||
<fo:inline font-size="10pt"><fo:block>
|
||||
|
||||
</fo:block></fo:inline><fo:block>
|
||||
|
||||
|
||||
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="28pt" font-size="14pt"><fo:basic-link color="#0000ff" text-decoration="underline" internal-destination="navigation">Navigation</fo:basic-link></fo:block>
|
||||
|
||||
</fo:block><fo:inline font-size="10pt"><fo:block><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/">Technical Articles</fo:basic-link></fo:block></fo:inline>
|
||||
|
||||
|
||||
<fo:inline font-size="10pt"><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com">Home</fo:basic-link></fo:inline>
|
||||
|
||||
<fo:list-block provisional-label-separation="3pt" provisional-distance-between-starts="14pt">
|
||||
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/">Home</fo:basic-link></fo:block></fo:list-item-body></fo:list-item>
|
||||
</fo:list-block><!-- /#nav -->
|
||||
<fo:block>
|
||||
<fo:list-block provisional-label-separation="3pt" provisional-distance-between-starts="14pt">
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/?feed=rss2"></fo:basic-link></fo:block></fo:list-item-body></fo:list-item>
|
||||
series</fo:list-block></fo:block><!-- /#side-nav --><!-- /.menus -->
|
||||
|
||||
<fo:inline font-size="10pt"><fo:basic-link color="#0000ff" text-decoration="underline" internal-destination="top">Return to Content</fo:basic-link></fo:inline>
|
||||
|
||||
|
||||
|
||||
<!-- #content Starts -->
|
||||
<fo:inline font-size="10pt"><fo:block>
|
||||
|
||||
</fo:block></fo:inline><fo:block>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- #main Starts -->
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="32pt" font-size="16pt">Registration &#8211; Activate a New Account by Email</fo:block>
|
||||
|
||||
</fo:block><fo:block><fo:inline font-size="10pt"><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/?author=10">Elena</fo:basic-link></fo:inline> <fo:inline font-size="10pt">October 23, 2014</fo:inline> <fo:inline font-size="10pt"><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/?cat=9">Spring Security</fo:basic-link></fo:inline> </fo:block>
|
||||
<fo:inline font-size="10pt"></fo:inline><fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" line-height="32pt" font-size="16pt">1. Overview</fo:block>
|
||||
<fo:block line-height="12pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"><fo:inline font-size="10pt">This article continues our ongoing </fo:inline><fo:inline font-size="10pt">Registration with Spring Security</fo:inline><fo:inline font-size="10pt"> by finishing the missing piece of the registration process &#8211; </fo:inline><fo:inline font-size="10pt">verifying the email to confirm the user registration</fo:inline><fo:inline font-size="10pt">.</fo:inline></fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Confirm Registration&#8221; email sent after successful registration to verify his email address and activate his account. The user does this by clicking a unique account activation link sent to him as part of the email message.</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Following this logic, a newly registered user will not be able to log in until email/registration verification is completed.</fo:block>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" line-height="32pt" font-size="16pt">2. A Verification Token<fo:inline white-space-collapse="false">
|
||||
</fo:inline>
|
||||
Entity to Our Modelassociated to a . So, we need a one-to-one unidirectional association between the and the . Entity for the user and persisting it.valueas a parameter.</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">We will make use of a simple verification token as the key artifact through which a user is verified.</fo:block>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="28pt" font-size="14pt">2.1. Adding a VerificationToken entity must meet the following criteria:</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">The VerificationToken</fo:block>
|
||||
<fo:list-block provisional-label-separation="3pt" provisional-distance-between-starts="14pt">
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">There will be one VerificationToken User VerificationTokenUser</fo:block></fo:list-item-body></fo:list-item>
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">It will be created after the user registration data is persisted.</fo:block></fo:list-item-body></fo:list-item>
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">It will expire in 24 hours following initial registration.</fo:block></fo:list-item-body></fo:list-item>
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Its value should be unique and randomly generated.</fo:block></fo:list-item-body></fo:list-item>
|
||||
entity like the one in Example 2.1.:</fo:list-block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Requirements 2 and 3 are part of the registration logic. The other two are implemented in a simple VerificationToken</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Example 2.1.</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">@Entity
|
||||
@Table
|
||||
public class VerificationToken {
|
||||
private static final int EXPIRATION = 60 * 24;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "token")
|
||||
private String token;
|
||||
|
||||
@OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "user_id")
|
||||
private User user;
|
||||
|
||||
@Column(name = "expiry_date")
|
||||
private Date expiryDate;
|
||||
|
||||
public VerificationToken() {
|
||||
super();
|
||||
}
|
||||
public VerificationToken(String token, User user) {
|
||||
super();
|
||||
this.token = token;
|
||||
this.user = user;
|
||||
this.expiryDate = calculateExpiryDate(EXPIRATION);
|
||||
this.verified = false;
|
||||
}
|
||||
private Date calculateExpiryDate(int expiryTimeInMinutes) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(new Timestamp(cal.getTime().getTime()));
|
||||
cal.add(Calendar.MINUTE, expiryTimeInMinutes);
|
||||
return new Date(cal.getTime().getTime());
|
||||
}
|
||||
|
||||
// standard getters and setters
|
||||
}</fo:block></fo:inline>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="28pt" font-size="14pt">2.2. Add an Enabled Flag to the User entity for now:</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">We will set the value of this flag depending on the result of the registration confirmation use case. Lets jus add the following field to our User</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">@Column(name = "enabled")
|
||||
private boolean enabled;</fo:block></fo:inline>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" line-height="32pt" font-size="16pt">3. The Account Registration Phase</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Lets add two additional pieces of business logic to the user registration use case:</fo:block>
|
||||
<fo:list-block provisional-label-separation="3pt" provisional-distance-between-starts="14pt">
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Generating a VerificationToken</fo:block></fo:list-item-body></fo:list-item>
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Sending the account confirmation email message which includes a confirmation link with the VerificationToken&#8217;s </fo:block></fo:list-item-body></fo:list-item>
|
||||
</fo:list-block>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="28pt" font-size="14pt">3.1. Using Spring Event Handling to Create the Token and Send the Verification Email to trigger the execution of these tasks. This is as simple as injecting anr in the controller, and then using it to publish the registration completion. Example 3.1. shows this simple logic:</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">These two additional pieces of logic should not be performed by the controller directly because they are &#8220;collateral&#8221; back-end tasks. The controller will publish a Spring ApplicationEvent ApplicationEventPublishe</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Example 3.1.</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">@Autowired
|
||||
ApplicationEventPublisher
|
||||
@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
|
||||
public ModelAndView registerUserAccount(@ModelAttribute("user") @Valid UserDto accountDto,
|
||||
BindingResult result, WebRequest request, Errors errors) {
|
||||
User registered = new User();
|
||||
String appUrl = request.getContextPath();
|
||||
if (result.hasErrors()) {
|
||||
return new ModelAndView("registration", "user", accountDto);
|
||||
}
|
||||
registered = createUserAccount(accountDto);
|
||||
if (registered == null) {
|
||||
result.rejectValue("email", "message.regError");
|
||||
}
|
||||
eventPublisher.publishEvent(new OnRegistrationCompleteEvent(registered,
|
||||
request.getLocale(), appUrl));
|
||||
return new ModelAndView("successRegister", "user", accountDto);
|
||||
}</fo:block></fo:inline>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="28pt" font-size="14pt">3.2. Spring Event Handler Implementation to start the that will handle the verification token creation and confirmation email sending. So it needs to have access to the implementation of the following interfaces:</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">The controller is using an ApplicationEventPublisherRegistrationListener</fo:block>
|
||||
<fo:list-block provisional-label-separation="3pt" provisional-distance-between-starts="14pt">
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">An AplicationEvent representing the completion of the user registration.</fo:block></fo:list-item-body></fo:list-item>
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">An ApplicationListener. For and access. and its implementation for new CRUD operations needed.</fo:block></fo:list-item-body></fo:list-item>
|
||||
, and the shown Examples 3.2.1 &#8211; 3.2.2.</fo:list-block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">The beans we will create are the OnRegistrationCompleteEventRegistrationListener</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">OnRegistrationCompleteEvent Example 3.2.1.</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">@SuppressWarnings("serial")
|
||||
public class OnRegistrationCompleteEvent extends ApplicationEvent {
|
||||
private final String appUrl;
|
||||
private final Locale locale;
|
||||
private final User user;
|
||||
|
||||
public OnRegistrationCompleteEvent(User user, Locale locale, String appUrl) {
|
||||
super(user);
|
||||
this.user = user;
|
||||
this.locale = locale;
|
||||
this.appUrl = appUrl;
|
||||
}
|
||||
|
||||
// standard getters and setters
|
||||
}</fo:block></fo:inline>
|
||||
<fo:block line-height="12pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"><fo:inline font-size="10pt">OnRegistrationCompleteEvent </fo:inline><fo:inline font-size="10pt">Example 3.2.2. </fo:inline><fo:inline font-size="10pt">- </fo:inline><fo:inline font-size="10pt">The RegistrationListener</fo:inline><fo:inline font-size="10pt"> method will receive the , extract all the necessary information from it, create the verification token, persist it, and then send it as a parameter in the &#8220;Confirm Registration&#8221; link sent to the user.</fo:inline></fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">@Component
|
||||
public class RegistrationListener implements ApplicationListener<OnRegistrationCompleteEvent> {
|
||||
@Autowired
|
||||
private IUserService service;
|
||||
|
||||
@Autowired
|
||||
private MessageSource messages;
|
||||
|
||||
@Autowired
|
||||
private JavaMailSender mailSender;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(OnRegistrationCompleteEvent event) {
|
||||
this.confirmRegistration(event);
|
||||
}
|
||||
|
||||
private void confirmRegistration(OnRegistrationCompleteEvent event) {
|
||||
User user = event.getUser();
|
||||
String token = UUID.randomUUID().toString();
|
||||
service.addVerificationToken(user, token);
|
||||
String recipientAddress = user.getEmail();
|
||||
String subject = "Registration Confirmation";
|
||||
String confirmationUrl = event.getAppUrl() + "/regitrationConfirm.html?token=" + token;
|
||||
String message = messages.getMessage("message.regSucc", null, event.getLocale());
|
||||
SimpleMailMessage email = new SimpleMailMessage();
|
||||
email.setTo(recipientAddress);
|
||||
email.setSubject(subject);
|
||||
email.setText(message + " \r\n" + "http://localhost:8080" + confirmationUrl);
|
||||
mailSender.send(email);
|
||||
}
|
||||
}</fo:block></fo:inline>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Here, the confirmRegistrationOnRegistrationCompleteEventUser</fo:block>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="28pt" font-size="14pt">3.3. Processing the Verification Token Parameter</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">When the user receives the &#8220;Confirm Registration&#8221; email, he will click on the attached link and fire a GET request. The controller will extract the value of the token parameter in the GET request and will use it to verify the user. Lets see this logic in Example 3.3.1.</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Example 3.3.1. &#8211; RegistrationController or if the does not exist, the controller will return a page with the corresponding error message (See Example 3.3.2.).</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">private IUserService service;
|
||||
|
||||
@Autowired
|
||||
public RegistrationController(IUserService service){
|
||||
this.service = service
|
||||
}
|
||||
@RequestMapping(value = "/regitrationConfirm", method = RequestMethod.GET)
|
||||
public String confirmRegistration(WebRequest request, Model model,
|
||||
@RequestParam("token") String token) {
|
||||
VerificationToken verificationToken = service.getVerificationToken(token);
|
||||
if (verificationToken == null) {
|
||||
model.addAttribute("message", messages.getMessage("auth.message.invalidToken",
|
||||
null, request.getLocale()));
|
||||
return "redirect:/badUser.html?lang=" + request.getLocale().getLanguage();
|
||||
}
|
||||
User user = verificationToken.getUser();
|
||||
Calendar cal = Calendar.getInstance();
|
||||
if (user == null) {
|
||||
model.addAttribute("message", messages.getMessage("auth.message.invalidUser",
|
||||
null, request.getLocale()));
|
||||
return "redirect:/badUser.html?lang=" + request.getLocale().getLanguage();
|
||||
}
|
||||
if ((verificationToken.getExpiryDate().getTime() - cal.getTime().getTime()) <= 0) {
|
||||
user.setEnabled(false);
|
||||
} else {
|
||||
user.setEnabled(true);
|
||||
}
|
||||
service.saveRegisteredUser(user);
|
||||
return "redirect:/login.html?lang=" + request.getLocale().getLanguage();
|
||||
}</fo:block></fo:inline>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Notice that if there is no user associated with the VerificationTokenVerificationToken badUser.html</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Example 3.3.2. &#8211; The badUser.html&#8216;s field after checking if the has expired.</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false"><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
|
||||
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
|
||||
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
|
||||
<fmt:setBundle basename="messages" />
|
||||
<%@ page session="true"%>
|
||||
<html>
|
||||
<head>
|
||||
<link href="<c:url value="/resources/bootstrap.css" />&quot; rel="stylesheet">
|
||||
<title>Expired</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${message}</h1>
|
||||
<br>
|
||||
<a href="<c:url value="/user/registration" />&quot;>
|
||||
<spring:message code="label.form.loginSignUp"></spring:message>
|
||||
</a>
|
||||
</body>
|
||||
</html></fo:block></fo:inline>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">If the token and user exist, the controller then proceeds to set the UserenabledVerificationToken</fo:block>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" line-height="32pt" font-size="16pt">4. Adding Account Activation Checking to the Login Process</fo:block>
|
||||
<fo:block line-height="12pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"><fo:inline font-size="10pt">l</fo:inline><fo:inline font-size="10pt">adUserByUsername</fo:inline><fo:inline font-size="10pt"> method:</fo:inline></fo:block>
|
||||
<fo:list-block provisional-label-separation="3pt" provisional-distance-between-starts="14pt">
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Make sure that the user is enabled before letting him log in.</fo:block></fo:list-item-body></fo:list-item>
|
||||
check.</fo:list-block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Example 4.1. shows the simple isEnabled()</fo:block>
|
||||
<fo:block line-height="12pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"><fo:inline font-size="10pt">Example 4.1. &#8211; </fo:inline><fo:inline font-size="10pt">Checking the VerificationToken</fo:inline><fo:inline font-size="10pt"> in MyUserDetailsService</fo:inline><fo:inline font-size="10pt"> with the flag set to false. This will trigger a </fo:inline></fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">private UserRepository userRepository;
|
||||
@Autowired
|
||||
private IUserService service;
|
||||
@Autowired
|
||||
private MessageSource messages;
|
||||
|
||||
@Autowired
|
||||
public MyUserDetailsService(UserRepository repository) {
|
||||
this.userRepository = repository;
|
||||
}
|
||||
|
||||
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||
boolean enabled = true;
|
||||
boolean accountNonExpired = true;
|
||||
boolean credentialsNonExpired = true;
|
||||
boolean accountNonLocked = true;
|
||||
try {
|
||||
User user = userRepository.findByEmail(email);
|
||||
if (user == null) {
|
||||
return new org.springframework.security.core.userdetails.User(" ", " ", enabled,
|
||||
true, true, true, getAuthorities(new Integer(1)));
|
||||
}
|
||||
if (!user.isEnabled()) {
|
||||
accountNonExpired = false;
|
||||
service.deleteUser(user);
|
||||
return new org.springframework.security.core.userdetails.User(" ", " ", enabled,
|
||||
accountNonExpired, true, true, getAuthorities(new Integer(1)));
|
||||
}
|
||||
return new org.springframework.security.core.userdetails.User(user.getEmail(),
|
||||
user.getPassword().toLowerCase(),
|
||||
enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
|
||||
getAuthorities(user.getRole().getRole()));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}</fo:block></fo:inline>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Notice that if the user is not enabled, the account is deleted and the method returns an org.springframework.security.core.userdetails.UseraccountNonExpiredUser Account Has ExpiredSPRING_SECURITY_LAST_EXCEPTION</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Now, we need to modify our login.htmllogin.html:</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">ogin.htmlExample 4.2. &#8211; Adding Account Activation Error Checking t</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false"><c:if test="${param.error != null}">
|
||||
<c:choose>
|
||||
<c:when test="${SPRING_SECURITY_LAST_EXCEPTION.message == &#039;User is disabled&#039;}">
|
||||
<div class="alert alert-error">
|
||||
<spring:message code="auth.message.disabled"></spring:message>
|
||||
</div>
|
||||
</c:when>
|
||||
<c:when test="${SPRING_SECURITY_LAST_EXCEPTION.message == &#039;User account has expired&#039;}">
|
||||
<div class="alert alert-error">
|
||||
<spring:message code="auth.message.expired"></spring:message>
|
||||
</div>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<div class="alert alert-error">
|
||||
<spring:message code="message.badCredentials"></spring:message>
|
||||
</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if></fo:block></fo:inline>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" line-height="32pt" font-size="16pt">5. Adapting the Persistence Layer</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">We need to modify the API of the persistence layer by:</fo:block>
|
||||
<fo:list-block provisional-label-separation="3pt" provisional-distance-between-starts="14pt">
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Creating a VerificationTokenRepository UserVerificationToken</fo:block></fo:list-item-body></fo:list-item>
|
||||
<fo:list-item><fo:list-item-label><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">-</fo:block></fo:list-item-label>
|
||||
<fo:list-item-body start-indent="body-start()"><fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Adding methods to the IUserInterface</fo:block></fo:list-item-body></fo:list-item>
|
||||
</fo:list-block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Examples 5.1 &#8211; 5.3. show the new interfaces and implementation:</fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">VerificationTokenRepositoryExample 5.1.</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">public interface VerificationTokenRepository extends JpaRepository<VerificationToken, Long> {
|
||||
|
||||
VerificationToken findByToken(String token);
|
||||
|
||||
VerificationToken findByUser(User user);
|
||||
}</fo:block></fo:inline>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">IUserServiceExample 5.2. &#8211; The Interface</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">public interface IUserService {
|
||||
|
||||
User registerNewUserAccount(UserDto accountDto) throws EmailExistsException;
|
||||
|
||||
User getUser(String verificationToken);
|
||||
|
||||
void saveRegisteredUser(User user);
|
||||
|
||||
void addVerificationToken(User user, String token);
|
||||
|
||||
VerificationToken getVerificationToken(String VerificationToken);
|
||||
|
||||
void deleteUser(User user);
|
||||
}</fo:block></fo:inline>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">UserService Example 5.3.</fo:block>
|
||||
<fo:inline font-size="10pt" font-family="Courier"><fo:block font-family="Courier" white-space-collapse="false">@Service
|
||||
public class UserService implements IUserService {
|
||||
@Autowired
|
||||
private UserRepository repository;
|
||||
|
||||
@Autowired
|
||||
private VerificationTokenRepository tokenRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException {
|
||||
if (emailExist(accountDto.getEmail())) {
|
||||
throw new EmailExistsException("There is an account with that email adress: " +
|
||||
accountDto.getEmail());
|
||||
}
|
||||
User user = new User();
|
||||
user.setFirstName(accountDto.getFirstName());
|
||||
user.setLastName(accountDto.getLastName());
|
||||
user.setPassword(accountDto.getPassword());
|
||||
user.setEmail(accountDto.getEmail());
|
||||
user.setRole(new Role(Integer.valueOf(1), user));
|
||||
return repository.save(user);
|
||||
}
|
||||
|
||||
private boolean emailExist(String email) {
|
||||
User user = repository.findByEmail(email);
|
||||
if (user != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUser(String verificationToken) {
|
||||
User user = tokenRepository.findByToken(verificationToken).getUser();
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerificationToken getVerificationToken(String VerificationToken) {
|
||||
return tokenRepository.findByToken(VerificationToken);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void saveRegisteredUser(User user) {
|
||||
repository.save(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void deleteUser(User user) {
|
||||
repository.delete(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void addVerificationToken(User user, String token) {
|
||||
VerificationToken myToken = new VerificationToken(token, user);
|
||||
tokenRepository.save(myToken);
|
||||
}
|
||||
}</fo:block></fo:inline>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" line-height="32pt" font-size="16pt">6. Conclusion</fo:block>
|
||||
<fo:block line-height="12pt" font-style="italic" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">We have expanded our Spring registration process to include an email based account activation procedure. The account activation logic requires sending a verification token to the user via email, so that he can send it back to the controller to verify his identity. A Spring event handler layer </fo:block>
|
||||
<fo:inline font-size="10pt"></fo:inline><fo:inline font-size="10pt"><fo:block></fo:block></fo:inline><fo:inline font-size="10pt"><fo:block></fo:block></fo:inline><fo:inline font-size="10pt"><fo:block></fo:block></fo:inline><fo:inline font-size="10pt"><fo:block></fo:block></fo:inline><fo:inline font-size="10pt"><fo:block></fo:block></fo:inline><fo:inline font-size="10pt"><fo:block><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://twitter.com/share"></fo:basic-link></fo:block></fo:inline><fo:inline font-size="10pt"><fo:block></fo:block></fo:inline><!-- /.entry -->
|
||||
<fo:inline font-size="10pt"><fo:block></fo:block></fo:inline>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="28pt" font-size="14pt">Subscribe</fo:block>
|
||||
|
||||
<fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">Subscribe to our e-mail newsletter to receive updates.</fo:block></fo:block>
|
||||
|
||||
|
||||
|
||||
<fo:block>
|
||||
<fo:inline font-size="10pt"><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/?feed=rss2"></fo:basic-link></fo:inline></fo:block><!-- col-left -->
|
||||
|
||||
|
||||
<fo:inline font-size="10pt"><fo:block></fo:block></fo:inline>
|
||||
|
||||
<fo:inline font-size="10pt"><fo:block></fo:block></fo:inline><!-- /.post -->
|
||||
<fo:inline font-size="10pt"><fo:block>
|
||||
</fo:block></fo:inline><fo:inline font-style="italic" font-size="10pt"><fo:block><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/?p=653"> (published) Handling Static Resources With Spring</fo:basic-link></fo:block></fo:inline>
|
||||
<fo:inline font-style="italic" font-size="10pt"><fo:block><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/?p=1430">Convert HTML to PDF using Apache FOP </fo:basic-link></fo:block></fo:inline>
|
||||
<fo:inline font-size="10pt"><fo:block></fo:block></fo:inline>
|
||||
|
||||
|
||||
<fo:inline font-size="10pt"><fo:block><fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="24pt" font-size="12pt">No comments yet.</fo:block></fo:block></fo:inline> <fo:block>
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" font-weight="bold" line-height="28pt" font-size="14pt">Leave a Reply <fo:basic-link color="#0000ff" text-decoration="underline" external-destination="/?p=1092#respond">Click here to cancel reply.</fo:basic-link></fo:block></fo:block>
|
||||
<fo:block line-height="12pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"><fo:inline font-size="10pt">Logged in as </fo:inline><fo:inline font-size="10pt"><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/wp-admin/profile.php">odeskAuthor8</fo:basic-link></fo:inline><fo:inline font-size="10pt">. </fo:inline><fo:inline font-size="10pt"><fo:basic-link color="#0000ff" text-decoration="underline" external-destination="http://inprogress.baeldung.com/wp-login.php?action=logout">Log out?</fo:basic-link></fo:inline></fo:block> <fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"></fo:block> <fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always">
|
||||
|
||||
|
||||
|
||||
</fo:block><!-- #respond --><!-- /#main --><!-- /#main-sidebar-container --><!-- /#content -->
|
||||
|
||||
<fo:block>
|
||||
<fo:block line-height="12pt" font-size="10pt" space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always"><3E> 2014 Technical Articles. All Rights Reserved. </fo:block></fo:block>
|
||||
|
||||
<fo:inline font-size="10pt"><fo:block>
|
||||
</fo:block></fo:inline><!-- /#inner-wrapper --><!-- /#wrapper -->
|
||||
|
||||
<fo:inline font-size="10pt"><fo:block></fo:block></fo:inline><!--/.fix-->
|
||||
|
||||
|
||||
|
||||
<fo:block space-before.optimum="1.5pt" space-after.optimum="1.5pt" keep-together="always" id="LastPage" line-height="1pt" font-size="1pt"></fo:block></fo:block></fo:flow>
|
||||
</fo:page-sequence></fo:root>
|
|
@ -0,0 +1,598 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Registration – Activate a New Account by Email | Technical Articles</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<link rel="pingback" href="http://inprogress.baeldung.com/xmlrpc.php" />
|
||||
<meta name='robots' content='noindex,follow' />
|
||||
|
||||
<!-- Mobile viewport scale -->
|
||||
<meta content="initial-scale=1.0, maximum-scale=1.0, user-scalable=yes" name="viewport"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="Technical Articles » Feed" href="http://inprogress.baeldung.com/?feed=rss2" />
|
||||
<link rel="alternate" type="application/rss+xml" title="Technical Articles » Comments Feed" href="http://inprogress.baeldung.com/?feed=comments-rss2" />
|
||||
<link rel="alternate" type="application/rss+xml" title="Technical Articles » Registration – Activate a New Account by Email Comments Feed" href="http://inprogress.baeldung.com/?feed=rss2&p=1092" />
|
||||
<link rel='stylesheet' id='open-sans-css' href='//fonts.googleapis.com/css?family=Open+Sans%3A300italic%2C400italic%2C600italic%2C300%2C400%2C600&subset=latin%2Clatin-ext&ver=4.0.1' type='text/css' media='all' />
|
||||
<link rel='stylesheet' id='dashicons-css' href='http://inprogress.baeldung.com/wp-includes/css/dashicons.min.css?ver=4.0.1' type='text/css' media='all' />
|
||||
<link rel='stylesheet' id='admin-bar-css' href='http://inprogress.baeldung.com/wp-includes/css/admin-bar.min.css?ver=4.0.1' type='text/css' media='all' />
|
||||
<link rel='stylesheet' id='digg-digg-css' href='http://inprogress.baeldung.com/wp-content/plugins/digg-digg/css/diggdigg-style.css?ver=5.3.6' type='text/css' media='screen' />
|
||||
<link rel='stylesheet' id='theme-stylesheet-css' href='http://inprogress.baeldung.com/wp-content/themes/canvas-child/style.css?ver=5.8.0' type='text/css' media='all' />
|
||||
<link rel='stylesheet' id='core3.0-css' href='http://inprogress.baeldung.com/wp-content/plugins/wp-syntaxhighlighter/syntaxhighlighter3/styles/shCore.css?ver=3.0' type='text/css' media='all' />
|
||||
<link rel='stylesheet' id='core-Default3.0-css' href='http://inprogress.baeldung.com/wp-content/plugins/wp-syntaxhighlighter/syntaxhighlighter3/styles/shCoreDefault.css?ver=3.0' type='text/css' media='all' />
|
||||
<link rel='stylesheet' id='theme-Default3.0-css' href='http://inprogress.baeldung.com/wp-content/plugins/wp-syntaxhighlighter/syntaxhighlighter3/styles/shThemeDefault.css?ver=3.0' type='text/css' media='all' />
|
||||
<!--[if lt IE 9]>
|
||||
<link href="http://inprogress.baeldung.com/wp-content/themes/canvas/css/non-responsive.css" rel="stylesheet" type="text/css" />
|
||||
<style type="text/css">.col-full, #wrapper { width: 1150px; max-width: 1150px; } #inner-wrapper { padding: 0; } body.full-width #header, #nav-container, body.full-width #content, body.full-width #footer-widgets, body.full-width #footer { padding-left: 0; padding-right: 0; } body.fixed-mobile #top, body.fixed-mobile #header-container, body.fixed-mobile #footer-container, body.fixed-mobile #nav-container, body.fixed-mobile #footer-widgets-container { min-width: 1150px; padding: 0 1em; } body.full-width #content { width: auto; padding: 0 1em;}</style>
|
||||
<![endif]-->
|
||||
<script type='text/javascript' src='http://inprogress.baeldung.com/wp-includes/js/jquery/jquery.js?ver=1.11.1'></script>
|
||||
<script type='text/javascript' src='http://inprogress.baeldung.com/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1'></script>
|
||||
<script type='text/javascript' src='http://inprogress.baeldung.com/wp-content/plugins/q2w3-fixed-widget/js/q2w3-fixed-widget.min.js?ver=4.0.6'></script>
|
||||
<script type='text/javascript' src='http://inprogress.baeldung.com/wp-content/themes/canvas/includes/js/third-party.min.js?ver=4.0.1'></script>
|
||||
<script type='text/javascript' src='http://inprogress.baeldung.com/wp-content/themes/canvas/includes/js/modernizr.min.js?ver=2.6.2'></script>
|
||||
<script type='text/javascript' src='http://inprogress.baeldung.com/wp-content/themes/canvas/includes/js/general.min.js?ver=4.0.1'></script>
|
||||
|
||||
<!-- Adjust the website width -->
|
||||
<style type="text/css">
|
||||
.col-full, #wrapper { max-width: 1150px !important; }
|
||||
</style>
|
||||
|
||||
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://inprogress.baeldung.com/xmlrpc.php?rsd" />
|
||||
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://inprogress.baeldung.com/wp-includes/wlwmanifest.xml" />
|
||||
<link rel='prev' title='(published) Handling Static Resources With Spring' href='http://inprogress.baeldung.com/?p=653' />
|
||||
<link rel='next' title='Convert HTML to PDF using Apache FOP' href='http://inprogress.baeldung.com/?p=1430' />
|
||||
<meta name="generator" content="WordPress 4.0.1" />
|
||||
<link rel='shortlink' href='http://inprogress.baeldung.com/?p=1092' />
|
||||
|
||||
<!-- Custom CSS Styling -->
|
||||
<style type="text/css">
|
||||
body {background-repeat:no-repeat;background-position:top left;background-attachment:scroll;border-top:0px solid #000000;}
|
||||
#header {background-repeat:no-repeat;background-position:left top;margin-top:0px;margin-bottom:0px;padding-top:10px;padding-bottom:10px;border:0px solid ;}
|
||||
#logo .site-title a {font:normal 40px/1em Arial, sans-serif;color:#222222;}
|
||||
#logo .site-description {font:300 13px/1em "Helvetica Neue", Helvetica, sans-serif;color:#999999;}
|
||||
body, p { font:normal 15px/1.5em Arial, sans-serif;color:#000000; }
|
||||
h1 { font:normal 28px/1.2em Arial, sans-serif;color:#222222; }h2 { font:normal 24px/1.2em Arial, sans-serif;color:#222222; }h3 { font:normal 20px/1.2em Arial, sans-serif;color:#222222; }h4 { font:normal 16px/1.2em Arial, sans-serif;color:#222222; }h5 { font:normal 14px/1.2em Arial, sans-serif;color:#222222; }h6 { font:normal 12px/1.2em Arial, sans-serif;color:#222222; }
|
||||
.page-title, .post .title, .page .title {font:bold 30px/1.1em Arial, sans-serif;color:#222222;}
|
||||
.post .title a:link, .post .title a:visited, .page .title a:link, .page .title a:visited {color:#222222}
|
||||
.post-meta { font:normal 11px/1.5em Arial, sans-serif;color:#868686; }
|
||||
.entry, .entry p{ font:normal 16px/1.5em Arial, sans-serif;color:#262626; }
|
||||
.post-more {font:normal 12px/1.5em Arial, sans-serif;color:#868686;border-top:4px solid #e6e6e6;border-bottom:0px solid #e6e6e6;}
|
||||
#post-author, #connect {border-top:1px solid #e6e6e6;border-bottom:1px solid #e6e6e6;border-left:1px solid #e6e6e6;border-right:1px solid #e6e6e6;border-radius:5px;-moz-border-radius:5px;-webkit-border-radius:5px;background-color:#fafafa}
|
||||
.nav-entries, .woo-pagination {border-top:1px solid #e6e6e6;border-bottom:4px solid #e6e6e6; padding: 12px 0px; }
|
||||
.nav-entries a, .woo-pagination { font:italic 12px/1em Arial, sans-serif;color:#777777; }
|
||||
.woo-pagination a, .woo-pagination a:hover {color:#777777!important}
|
||||
.widget h3 {font:bold 14px/1.2em Arial, sans-serif;color:#555555;border-bottom:1px solid #e6e6e6;}
|
||||
.widget_recent_comments li, #twitter li { border-color: #e6e6e6;}
|
||||
.widget p, .widget .textwidget { font:normal 12px/1.5em Arial, sans-serif;color:#555555; }
|
||||
.widget {font:normal 12px/1.5em Arial, sans-serif;color:#555555;border-radius:1px;-moz-border-radius:1px;-webkit-border-radius:1px;}
|
||||
#tabs .inside li a, .widget_woodojo_tabs .tabbable .tab-pane li a { font:bold 12px/1.5em "Helvetica Neue", Helvetica, sans-serif;color:#555555; }
|
||||
#tabs .inside li span.meta, .widget_woodojo_tabs .tabbable .tab-pane li span.meta { font:300 11px/1.5em "Helvetica Neue", Helvetica, sans-serif;color:#999999; }
|
||||
#tabs ul.wooTabs li a, .widget_woodojo_tabs .tabbable .nav-tabs li a { font:300 11px/2em "Helvetica Neue", Helvetica, sans-serif;color:#999999; }
|
||||
@media only screen and (min-width:768px) {
|
||||
ul.nav li a, #navigation ul.rss a, #navigation ul.cart a.cart-contents, #navigation .cart-contents #navigation ul.rss, #navigation ul.nav-search, #navigation ul.nav-search a { font:bold 15px/1.2em Arial, sans-serif;color:#555555; } #navigation ul.rss li a:before, #navigation ul.nav-search a.search-contents:before { color:#555555;}
|
||||
#navigation ul.nav li ul, #navigation ul.cart > li > ul > div { border: 1px solid #dbdbdb; }
|
||||
#navigation ul.nav > li { border-right: 1px solid #dbdbdb; }#navigation ul li:first-child, #navigation ul li:first-child a { border-radius:5px 0 0 5px; -moz-border-radius:5px 0 0 5px; -webkit-border-radius:5px 0 0 5px; }
|
||||
#navigation {border-top:1px solid #dbdbdb;border-bottom:1px solid #dbdbdb;border-left:1px solid #dbdbdb;border-right:1px solid #dbdbdb;border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
|
||||
#top ul.nav li a { font:normal 14px/1.6em Arial, sans-serif;color:#ddd; }
|
||||
}
|
||||
#footer, #footer p { font:italic 13px/1.4em Arial, sans-serif;color:#777777; }
|
||||
#footer {border-top:4px solid #dbdbdb;border-bottom:0px solid ;border-left:0px solid ;border-right:0px solid ;border-radius:1px; -moz-border-radius:1px; -webkit-border-radius:1px;}
|
||||
.magazine #loopedSlider .content h2.title a { font:bold 24px/1em Arial, sans-serif;color:#ffffff; }
|
||||
.wooslider-theme-magazine .slide-title a { font:bold 24px/1em Arial, sans-serif;color:#ffffff; }
|
||||
.magazine #loopedSlider .content .excerpt p { font:300 13px/1.5em Arial, sans-serif;color:#cccccc; }
|
||||
.wooslider-theme-magazine .slide-content p, .wooslider-theme-magazine .slide-excerpt p { font:300 13px/1.5em Arial, sans-serif;color:#cccccc; }
|
||||
.magazine .block .post .title a {font:bold 18px/1.2em "Helvetica Neue", Helvetica, sans-serif;color:#222222; }
|
||||
#loopedSlider.business-slider .content h2 { font:bold 24px/1em Arial, sans-serif;color:#ffffff; }
|
||||
#loopedSlider.business-slider .content h2.title a { font:bold 24px/1em Arial, sans-serif;color:#ffffff; }
|
||||
.wooslider-theme-business .has-featured-image .slide-title { font:bold 24px/1em Arial, sans-serif;color:#ffffff; }
|
||||
.wooslider-theme-business .has-featured-image .slide-title a { font:bold 24px/1em Arial, sans-serif;color:#ffffff; }
|
||||
#wrapper #loopedSlider.business-slider .content p { font:300 13px/1.5em Arial, sans-serif;color:#cccccc; }
|
||||
.wooslider-theme-business .has-featured-image .slide-content p { font:300 13px/1.5em Arial, sans-serif;color:#cccccc; }
|
||||
.wooslider-theme-business .has-featured-image .slide-excerpt p { font:300 13px/1.5em Arial, sans-serif;color:#cccccc; }
|
||||
.archive_header { font:bold 18px/1em Arial, sans-serif;color:#222222; }
|
||||
.archive_header {border-bottom:1px solid #e6e6e6;}
|
||||
</style>
|
||||
|
||||
<!-- Woo Shortcodes CSS -->
|
||||
<link href="http://inprogress.baeldung.com/wp-content/themes/canvas/functions/css/shortcodes.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<!-- Custom Stylesheet -->
|
||||
<link href="http://inprogress.baeldung.com/wp-content/themes/canvas/custom.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<!-- Theme version -->
|
||||
<meta name="generator" content="Canvas Child 5.6.3" />
|
||||
<meta name="generator" content="Canvas 5.8.4" />
|
||||
<meta name="generator" content="WooFramework 6.0.4" />
|
||||
<!-- All in one Favicon 4.3 -->
|
||||
<!-- All in One SEO Pack 2.2.3.1 by Michael Torbert of Semper Fi Web Design[68,148] -->
|
||||
<meta name="description" content="1. Overview This article continues our ongoing Registration with Spring Security series by finishing the missing piece of the registration process - verifying" />
|
||||
|
||||
<link rel="canonical" href="http://inprogress.baeldung.com/?p=1092" />
|
||||
<!-- /all in one seo pack -->
|
||||
<style type="text/css" media="print">#wpadminbar { display:none; }</style>
|
||||
<style type="text/css" media="screen">
|
||||
html { margin-top: 32px !important; }
|
||||
* html body { margin-top: 32px !important; }
|
||||
@media screen and ( max-width: 782px ) {
|
||||
html { margin-top: 46px !important; }
|
||||
* html body { margin-top: 46px !important; }
|
||||
}
|
||||
</style>
|
||||
<style type="text/css" id="syntaxhighlighteranchor"></style>
|
||||
</head>
|
||||
<body class="single single-post postid-1092 single-format-standard logged-in admin-bar no-customize-support chrome alt-style-default two-col-left width-1150 two-col-left-1150">
|
||||
<div id="wrapper">
|
||||
|
||||
<div id="inner-wrapper">
|
||||
|
||||
<h3 class="nav-toggle icon"><a href="#navigation">Navigation</a></h3>
|
||||
|
||||
<header id="header" class="col-full">
|
||||
|
||||
<div id="logo">
|
||||
<span class="site-title"><a href="http://inprogress.baeldung.com/">Technical Articles</a></span>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
<nav id="navigation" class="col-full" role="navigation">
|
||||
|
||||
|
||||
<section class="menus nav-icons nav-icons-1">
|
||||
|
||||
<a href="http://inprogress.baeldung.com" class="nav-home"><span>Home</span></a>
|
||||
|
||||
<ul id="main-nav" class="nav fl">
|
||||
|
||||
<li class="page_item current_page_item"><a href="http://inprogress.baeldung.com/">Home</a></li>
|
||||
</ul><!-- /#nav -->
|
||||
<div class="side-nav">
|
||||
<ul class="rss fr">
|
||||
<li class="sub-rss"><a href="http://inprogress.baeldung.com/?feed=rss2"></a></li>
|
||||
</ul>
|
||||
</div><!-- /#side-nav -->
|
||||
|
||||
</section><!-- /.menus -->
|
||||
|
||||
<a href="#top" class="nav-close"><span>Return to Content</span></a>
|
||||
|
||||
</nav>
|
||||
|
||||
<!-- #content Starts -->
|
||||
<div id="content" class="col-full">
|
||||
|
||||
<div id="main-sidebar-container">
|
||||
|
||||
<!-- #main Starts -->
|
||||
<section id="main">
|
||||
<article class="post-1092 post type-post status-publish format-standard hentry category-spring-security">
|
||||
<header>
|
||||
<span class="entry-title">
|
||||
<h1 class="title">Registration – Activate a New Account by Email</h1> </span>
|
||||
</header>
|
||||
<div class="post-meta"><span class="small">By</span> <span class="author vcard"><span class="fn"><a href="http://inprogress.baeldung.com/?author=10" title="Posts by Elena" rel="author">Elena</a></span></span> <span class="small">on</span> <abbr class="date time published" title="2014-10-23T12:36:14+0000">October 23, 2014</abbr> <span class="small">in</span> <span class="categories"><a href="http://inprogress.baeldung.com/?cat=9" rel="nofollow" title="View all items in Spring Security">Spring Security</a></span> </div>
|
||||
<section class="entry">
|
||||
<a id="dd_start"></a><h2><strong>1. Overview</strong></h2>
|
||||
<p>This article continues our ongoing <strong><em>Registration with Spring Security</em> series</strong> by finishing the missing piece of the registration process – <strong>verifying the email to confirm the user registration</strong>.</p>
|
||||
<p>The registration confirmation mechanism forces the user to respond to a “<em><strong>Confirm Registration</strong></em>” email sent after successful registration to verify his email address and activate his account. The user does this by clicking a unique account activation link sent to him as part of the email message.</p>
|
||||
<p>Following this logic, a newly registered user will not be able to log in until email/registration verification is completed.</p>
|
||||
<h2><strong>2. A Verification Token<br />
|
||||
</strong></h2>
|
||||
<p>We will make use of a simple verification token as the key artifact through which a user is verified.</p>
|
||||
<h3><strong>2.1. Adding a <em>VerificationToken</em> Entity to Our Model</strong></h3>
|
||||
<p>The <em>VerificationToken</em> entity must meet the following criteria:</p>
|
||||
<ol>
|
||||
<li>There will be one <em>VerificationToken </em>associated to a <em>User</em>. So, we need a one-to-one unidirectional association between the<em> VerificationToken</em> and the <em>User</em>.</li>
|
||||
<li>It will be created after the user registration data is persisted.</li>
|
||||
<li>It will expire in 24 hours following initial registration.</li>
|
||||
<li>Its value should be unique and randomly generated.</li>
|
||||
</ol>
|
||||
<p>Requirements 2 and 3 are part of the registration logic. The other two are implemented in a simple <em>VerificationToken</em> entity like the one in Example 2.1.:</p>
|
||||
<p><strong>Example 2.1.</strong></p>
|
||||
<pre class="brush: java; gutter: true">@Entity
|
||||
@Table
|
||||
public class VerificationToken {
|
||||
private static final int EXPIRATION = 60 * 24;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "token")
|
||||
private String token;
|
||||
|
||||
@OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "user_id")
|
||||
private User user;
|
||||
|
||||
@Column(name = "expiry_date")
|
||||
private Date expiryDate;
|
||||
|
||||
public VerificationToken() {
|
||||
super();
|
||||
}
|
||||
public VerificationToken(String token, User user) {
|
||||
super();
|
||||
this.token = token;
|
||||
this.user = user;
|
||||
this.expiryDate = calculateExpiryDate(EXPIRATION);
|
||||
this.verified = false;
|
||||
}
|
||||
private Date calculateExpiryDate(int expiryTimeInMinutes) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(new Timestamp(cal.getTime().getTime()));
|
||||
cal.add(Calendar.MINUTE, expiryTimeInMinutes);
|
||||
return new Date(cal.getTime().getTime());
|
||||
}
|
||||
|
||||
// standard getters and setters
|
||||
}</pre>
|
||||
<h3><strong>2.2. Add an Enabled Flag to the<em> User</em> Entity</strong></h3>
|
||||
<p>We will set the value of this flag depending on the result of the registration confirmation use case. Lets jus add the following field to our <em>User</em> entity for now:</p>
|
||||
<pre class="brush: java; gutter: true">@Column(name = "enabled")
|
||||
private boolean enabled;</pre>
|
||||
<h2><strong>3. The Account Registration Phase</strong></h2>
|
||||
<p>Lets add two additional pieces of business logic to the user registration use case:</p>
|
||||
<ol>
|
||||
<li>Generating a <em>VerificationToken</em> for the user and persisting it.</li>
|
||||
<li>Sending the account confirmation email message which includes a confirmation link with the <em>VerificationToken’s </em>value<em> </em>as a parameter.</li>
|
||||
</ol>
|
||||
<h3><strong>3.1. Using Spring Event Handling to Create the Token and Send the Verification Email</strong></h3>
|
||||
<p>These two additional pieces of logic should not be performed by the controller directly because they are “collateral” back-end tasks. The controller will publish a Spring <em>ApplicationEvent</em> to trigger the execution of these tasks. This is as simple as injecting an<em> ApplicationEventPublishe</em>r in the controller, and then using it to publish the registration completion. Example 3.1. shows this simple logic:</p>
|
||||
<p><strong>Example 3.1.</strong></p>
|
||||
<pre class="brush: java; gutter: true">@Autowired
|
||||
ApplicationEventPublisher
|
||||
@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
|
||||
public ModelAndView registerUserAccount(@ModelAttribute("user") @Valid UserDto accountDto,
|
||||
BindingResult result, WebRequest request, Errors errors) {
|
||||
User registered = new User();
|
||||
String appUrl = request.getContextPath();
|
||||
if (result.hasErrors()) {
|
||||
return new ModelAndView("registration", "user", accountDto);
|
||||
}
|
||||
registered = createUserAccount(accountDto);
|
||||
if (registered == null) {
|
||||
result.rejectValue("email", "message.regError");
|
||||
}
|
||||
eventPublisher.publishEvent(new OnRegistrationCompleteEvent(registered,
|
||||
request.getLocale(), appUrl));
|
||||
return new ModelAndView("successRegister", "user", accountDto);
|
||||
}</pre>
|
||||
<h3><strong>3.2. Spring Event Handler Implementation</strong></h3>
|
||||
<p>The controller is using an <em>ApplicationEventPublisher</em> to start the <em>RegistrationListener</em> that will handle the verification token creation and confirmation email sending. So it needs to have access to the implementation of the following interfaces:</p>
|
||||
<ol>
|
||||
<li>An <strong><em>AplicationEvent</em></strong> representing the completion of the user registration.</li>
|
||||
<li>An <strong><em>ApplicationListener</em></strong> bean which will listen to the published event and proceed to do all the work.</li>
|
||||
</ol>
|
||||
<p>The beans we will create are the <em>OnRegistrationCompleteEvent</em> , and the <em>RegistrationListener</em> shown Examples 3.2.1 – 3.2.2.</p>
|
||||
<p><strong>Example 3.2.1.</strong> – The <em>OnRegistrationCompleteEvent </em></p>
|
||||
<pre class="brush: javafx; gutter: true">@SuppressWarnings("serial")
|
||||
public class OnRegistrationCompleteEvent extends ApplicationEvent {
|
||||
private final String appUrl;
|
||||
private final Locale locale;
|
||||
private final User user;
|
||||
|
||||
public OnRegistrationCompleteEvent(User user, Locale locale, String appUrl) {
|
||||
super(user);
|
||||
this.user = user;
|
||||
this.locale = locale;
|
||||
this.appUrl = appUrl;
|
||||
}
|
||||
|
||||
// standard getters and setters
|
||||
}</pre>
|
||||
<p><strong>Example 3.2.2. </strong>- <strong><em>The RegistrationListener</em></strong> Responds to the <em>OnRegistrationCompleteEvent </em></p>
|
||||
<pre class="brush: java; gutter: true">@Component
|
||||
public class RegistrationListener implements ApplicationListener<OnRegistrationCompleteEvent> {
|
||||
@Autowired
|
||||
private IUserService service;
|
||||
|
||||
@Autowired
|
||||
private MessageSource messages;
|
||||
|
||||
@Autowired
|
||||
private JavaMailSender mailSender;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(OnRegistrationCompleteEvent event) {
|
||||
this.confirmRegistration(event);
|
||||
}
|
||||
|
||||
private void confirmRegistration(OnRegistrationCompleteEvent event) {
|
||||
User user = event.getUser();
|
||||
String token = UUID.randomUUID().toString();
|
||||
service.addVerificationToken(user, token);
|
||||
String recipientAddress = user.getEmail();
|
||||
String subject = "Registration Confirmation";
|
||||
String confirmationUrl = event.getAppUrl() + "/regitrationConfirm.html?token=" + token;
|
||||
String message = messages.getMessage("message.regSucc", null, event.getLocale());
|
||||
SimpleMailMessage email = new SimpleMailMessage();
|
||||
email.setTo(recipientAddress);
|
||||
email.setSubject(subject);
|
||||
email.setText(message + " \r\n" + "http://localhost:8080" + confirmationUrl);
|
||||
mailSender.send(email);
|
||||
}
|
||||
}</pre>
|
||||
<p>Here, the <em>confirmRegistration</em> method will receive the <em>OnRegistrationCompleteEvent</em>, extract all the necessary <em>User</em> information from it, create the verification token, persist it, and then send it as a parameter in the “Confirm Registration” link sent to the user.</p>
|
||||
<h3><strong>3.3. Processing the Verification Token Parameter</strong></h3>
|
||||
<p>When the user receives the “Confirm Registration” email, he will click on the attached link and fire a GET request. The controller will extract the value of the token parameter in the GET request and will use it to verify the user. Lets see this logic in Example 3.3.1.</p>
|
||||
<p><strong>Example 3.3.1. – <em>RegistrationController</em> Processing the Registration Confirmation Link</strong></p>
|
||||
<pre class="brush: java; gutter: true">private IUserService service;
|
||||
|
||||
@Autowired
|
||||
public RegistrationController(IUserService service){
|
||||
this.service = service
|
||||
}
|
||||
@RequestMapping(value = "/regitrationConfirm", method = RequestMethod.GET)
|
||||
public String confirmRegistration(WebRequest request, Model model,
|
||||
@RequestParam("token") String token) {
|
||||
VerificationToken verificationToken = service.getVerificationToken(token);
|
||||
if (verificationToken == null) {
|
||||
model.addAttribute("message", messages.getMessage("auth.message.invalidToken",
|
||||
null, request.getLocale()));
|
||||
return "redirect:/badUser.html?lang=" + request.getLocale().getLanguage();
|
||||
}
|
||||
User user = verificationToken.getUser();
|
||||
Calendar cal = Calendar.getInstance();
|
||||
if (user == null) {
|
||||
model.addAttribute("message", messages.getMessage("auth.message.invalidUser",
|
||||
null, request.getLocale()));
|
||||
return "redirect:/badUser.html?lang=" + request.getLocale().getLanguage();
|
||||
}
|
||||
if ((verificationToken.getExpiryDate().getTime() - cal.getTime().getTime()) <= 0) {
|
||||
user.setEnabled(false);
|
||||
} else {
|
||||
user.setEnabled(true);
|
||||
}
|
||||
service.saveRegisteredUser(user);
|
||||
return "redirect:/login.html?lang=" + request.getLocale().getLanguage();
|
||||
}</pre>
|
||||
<p>Notice that if there is no user associated with the <em>VerificationToken</em> or if the <em>VerificationToken</em> does not exist, the controller will return a<em> badUser.html</em> page with the corresponding error message (See Example 3.3.2.).</p>
|
||||
<p><strong>Example 3.3.2. – The<em> badUser.html</em></strong></p>
|
||||
<pre class="brush: javascript; gutter: true"><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
|
||||
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
|
||||
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
|
||||
<fmt:setBundle basename="messages" />
|
||||
<%@ page session="true"%>
|
||||
<html>
|
||||
<head>
|
||||
<link href="<c:url value="/resources/bootstrap.css" />" rel="stylesheet">
|
||||
<title>Expired</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${message}</h1>
|
||||
<br>
|
||||
<a href="<c:url value="/user/registration" />">
|
||||
<spring:message code="label.form.loginSignUp"></spring:message>
|
||||
</a>
|
||||
</body>
|
||||
</html></pre>
|
||||
<p>If the token and user exist, the controller then proceeds to set the <em>User</em>‘s <em>enabled</em> field after checking if the <em>VerificationToken</em> has expired.</p>
|
||||
<h2><strong>4. Adding Account Activation Checking to the Login Process</strong></h2>
|
||||
<p>We need to add the following verification logic to My<em>UserDetailsService’s </em><strong><em>l</em>o</strong><em><strong>adUserByUsername</strong></em> method:</p>
|
||||
<ul>
|
||||
<li>Make sure that the user is enabled before letting him log in.</li>
|
||||
</ul>
|
||||
<p>Example 4.1. shows the simple <em>isEnabled()</em> check.</p>
|
||||
<p><strong>Example 4.1. – </strong>Checking the VerificationToken<strong> in <em>MyUserDetailsService</em></strong></p>
|
||||
<pre class="brush: java; gutter: true">private UserRepository userRepository;
|
||||
@Autowired
|
||||
private IUserService service;
|
||||
@Autowired
|
||||
private MessageSource messages;
|
||||
|
||||
@Autowired
|
||||
public MyUserDetailsService(UserRepository repository) {
|
||||
this.userRepository = repository;
|
||||
}
|
||||
|
||||
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||
boolean enabled = true;
|
||||
boolean accountNonExpired = true;
|
||||
boolean credentialsNonExpired = true;
|
||||
boolean accountNonLocked = true;
|
||||
try {
|
||||
User user = userRepository.findByEmail(email);
|
||||
if (user == null) {
|
||||
return new org.springframework.security.core.userdetails.User(" ", " ", enabled,
|
||||
true, true, true, getAuthorities(new Integer(1)));
|
||||
}
|
||||
if (!user.isEnabled()) {
|
||||
accountNonExpired = false;
|
||||
service.deleteUser(user);
|
||||
return new org.springframework.security.core.userdetails.User(" ", " ", enabled,
|
||||
accountNonExpired, true, true, getAuthorities(new Integer(1)));
|
||||
}
|
||||
return new org.springframework.security.core.userdetails.User(user.getEmail(),
|
||||
user.getPassword().toLowerCase(),
|
||||
enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
|
||||
getAuthorities(user.getRole().getRole()));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}</pre>
|
||||
<p>Notice that if the user is not enabled, the account is deleted and the method returns an <em>org.springframework.security.core.userdetails.User</em> with the <em>accountNonExpired</em> flag set to false. This will trigger a <strong><em>SPRING_SECURITY_LAST_EXCEPTION</em></strong> in the login process. This exception’s String value is: ‘<em>User Account Has Expired</em>‘.</p>
|
||||
<p>Now, we need to modify our<em> login.html</em> page to show this and any other exception messages resulting from en email verification error. The error checking code we added to <em>login.html</em> is shown in Example 4.2.<em>:</em></p>
|
||||
<p><strong>Example 4.2. – Adding Account Activation Error Checking t</strong>o l<em>ogin.html</em></p>
|
||||
<pre class="brush: javascript; gutter: true"><c:if test="${param.error != null}">
|
||||
<c:choose>
|
||||
<c:when test="${SPRING_SECURITY_LAST_EXCEPTION.message == 'User is disabled'}">
|
||||
<div class="alert alert-error">
|
||||
<spring:message code="auth.message.disabled"></spring:message>
|
||||
</div>
|
||||
</c:when>
|
||||
<c:when test="${SPRING_SECURITY_LAST_EXCEPTION.message == 'User account has expired'}">
|
||||
<div class="alert alert-error">
|
||||
<spring:message code="auth.message.expired"></spring:message>
|
||||
</div>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<div class="alert alert-error">
|
||||
<spring:message code="message.badCredentials"></spring:message>
|
||||
</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if></pre>
|
||||
<h2>5. Adapting the Persistence Layer</h2>
|
||||
<p>We need to modify the API of the persistence layer by:</p>
|
||||
<ol>
|
||||
<li>Creating a <em>VerificationTokenRepository</em>. For<em> User</em> and <em>VerificationToken</em> access.</li>
|
||||
<li>Adding methods to the <em>IUserInterface</em> and its implementation for new CRUD operations needed.</li>
|
||||
</ol>
|
||||
<p>Examples 5.1 – 5.3. show the new interfaces and implementation:</p>
|
||||
<p><strong>Example 5.1.</strong> – The <em>VerificationTokenRepository</em></p>
|
||||
<pre class="brush: java; gutter: true">public interface VerificationTokenRepository extends JpaRepository<VerificationToken, Long> {
|
||||
|
||||
VerificationToken findByToken(String token);
|
||||
|
||||
VerificationToken findByUser(User user);
|
||||
}</pre>
|
||||
<p><strong>Example 5.2.</strong> – The <em>IUserService</em> Interface</p>
|
||||
<pre class="brush: java; gutter: true">public interface IUserService {
|
||||
|
||||
User registerNewUserAccount(UserDto accountDto) throws EmailExistsException;
|
||||
|
||||
User getUser(String verificationToken);
|
||||
|
||||
void saveRegisteredUser(User user);
|
||||
|
||||
void addVerificationToken(User user, String token);
|
||||
|
||||
VerificationToken getVerificationToken(String VerificationToken);
|
||||
|
||||
void deleteUser(User user);
|
||||
}</pre>
|
||||
<p><strong>Example 5.3.</strong> The <em>UserService </em></p>
|
||||
<pre class="brush: java; gutter: true">@Service
|
||||
public class UserService implements IUserService {
|
||||
@Autowired
|
||||
private UserRepository repository;
|
||||
|
||||
@Autowired
|
||||
private VerificationTokenRepository tokenRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException {
|
||||
if (emailExist(accountDto.getEmail())) {
|
||||
throw new EmailExistsException("There is an account with that email adress: " +
|
||||
accountDto.getEmail());
|
||||
}
|
||||
User user = new User();
|
||||
user.setFirstName(accountDto.getFirstName());
|
||||
user.setLastName(accountDto.getLastName());
|
||||
user.setPassword(accountDto.getPassword());
|
||||
user.setEmail(accountDto.getEmail());
|
||||
user.setRole(new Role(Integer.valueOf(1), user));
|
||||
return repository.save(user);
|
||||
}
|
||||
|
||||
private boolean emailExist(String email) {
|
||||
User user = repository.findByEmail(email);
|
||||
if (user != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUser(String verificationToken) {
|
||||
User user = tokenRepository.findByToken(verificationToken).getUser();
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerificationToken getVerificationToken(String VerificationToken) {
|
||||
return tokenRepository.findByToken(VerificationToken);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void saveRegisteredUser(User user) {
|
||||
repository.save(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void deleteUser(User user) {
|
||||
repository.delete(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void addVerificationToken(User user, String token) {
|
||||
VerificationToken myToken = new VerificationToken(token, user);
|
||||
tokenRepository.save(myToken);
|
||||
}
|
||||
}</pre>
|
||||
<h2>6. Conclusion</h2>
|
||||
<p>We have expanded our Spring registration process to include an email based account activation procedure. The account activation logic requires sending a verification token to the user via email, so that he can send it back to the controller to verify his identity. A <i>Spring event handler layer </i>takes care of the back-end work needed to send the confirmation email after the controller persists a registered.</p>
|
||||
<a id="dd_end"></a><div class='dd_outer'><div class='dd_inner'><div id='dd_ajax_float'><div class='dd_button_v'><script type='text/javascript' src='https://apis.google.com/js/plusone.js'></script><g:plusone size='tall' href='http://inprogress.baeldung.com/?p=1092'></g:plusone></div><div style='clear:left'></div><div class='dd_button_v'><a href="http://twitter.com/share" class="twitter-share-button" data-url="http://inprogress.baeldung.com/?p=1092" data-count="vertical" data-text="Registration - Activate a New Account by Email" data-via="" ></a><script type="text/javascript" src="//platform.twitter.com/widgets.js"></script></div><div style='clear:left'></div></div></div></div><script type="text/javascript">var dd_offset_from_content = 40;var dd_top_offset_from_content = 0;var dd_override_start_anchor_id = "";var dd_override_top_offset = "";</script><script type="text/javascript" src="http://inprogress.baeldung.com/wp-content/plugins/digg-digg//js/diggdigg-floating-bar.js?ver=5.3.6"></script> </section><!-- /.entry -->
|
||||
<div class="fix"></div>
|
||||
<aside id="connect">
|
||||
<h3>Subscribe</h3>
|
||||
|
||||
<div >
|
||||
<p>Subscribe to our e-mail newsletter to receive updates.</p>
|
||||
|
||||
|
||||
|
||||
<div class="social">
|
||||
<a href="http://inprogress.baeldung.com/?feed=rss2" class="subscribe" title="RSS"></a>
|
||||
|
||||
</div>
|
||||
|
||||
</div><!-- col-left -->
|
||||
|
||||
|
||||
<div class="fix"></div>
|
||||
</aside>
|
||||
<div class="post-utility"></div>
|
||||
</article><!-- /.post -->
|
||||
<div class="post-entries">
|
||||
<div class="nav-prev fl"><a href="http://inprogress.baeldung.com/?p=653" rel="prev"><i class="fa fa-angle-left"></i> (published) Handling Static Resources With Spring</a></div>
|
||||
<div class="nav-next fr"><a href="http://inprogress.baeldung.com/?p=1430" rel="next">Convert HTML to PDF using Apache FOP <i class="fa fa-angle-right"></i></a></div>
|
||||
<div class="fix"></div>
|
||||
</div>
|
||||
|
||||
<div id="comments"><h5 class="nocomments">No comments yet.</h5></div> <div id="respond" class="comment-respond">
|
||||
<h3 id="reply-title" class="comment-reply-title">Leave a Reply <small><a rel="nofollow" id="cancel-comment-reply-link" href="/?p=1092#respond" style="display:none;">Click here to cancel reply.</a></small></h3>
|
||||
<form action="http://inprogress.baeldung.com/wp-comments-post.php" method="post" id="commentform" class="comment-form">
|
||||
<p class="logged-in-as">Logged in as <a href="http://inprogress.baeldung.com/wp-admin/profile.php">odeskAuthor8</a>. <a href="http://inprogress.baeldung.com/wp-login.php?action=logout" title="Log out of this account">Log out?</a></p> <p class="comment-form-comment"><label class="hide" for="comment">Comment</label> <textarea tabindex="4" id="comment" name="comment" cols="50" rows="10" aria-required="true"></textarea></p> <p class="form-submit">
|
||||
<input name="submit" type="submit" id="submit" value="Submit Comment" />
|
||||
<input type='hidden' name='comment_post_ID' value='1092' id='comment_post_ID' />
|
||||
<input type='hidden' name='comment_parent' id='comment_parent' value='0' />
|
||||
</p>
|
||||
</form>
|
||||
</div><!-- #respond -->
|
||||
|
||||
</section><!-- /#main -->
|
||||
|
||||
|
||||
</div><!-- /#main-sidebar-container -->
|
||||
|
||||
|
||||
</div><!-- /#content -->
|
||||
|
||||
<footer id="footer" class="col-full">
|
||||
|
||||
|
||||
<div id="copyright" class="col-left">
|
||||
<p>© 2014 Technical Articles. All Rights Reserved. </p> </div>
|
||||
|
||||
<div id="credit" class="col-right">
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</div><!-- /#inner-wrapper -->
|
||||
|
||||
</div><!-- /#wrapper -->
|
||||
|
||||
<div class="fix"></div><!--/.fix-->
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,544 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<article version="5.0" xml:lang="en-US" xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink">
|
||||
<info>
|
||||
<title>Registration – Activate a New Account by Email | Technical Articles</title>
|
||||
<abstract>
|
||||
<para>1. Overview This article continues our ongoing Registration with Spring Security series by finishing the missing piece of the registration process - verifying</para>
|
||||
</abstract>
|
||||
</info>
|
||||
<!--
|
||||
Mobile viewport scale
|
||||
-->
|
||||
<anchor xml:id="open-sans-css"/>
|
||||
<anchor xml:id="dashicons-css"/>
|
||||
<anchor xml:id="admin-bar-css"/>
|
||||
<anchor xml:id="digg-digg-css"/>
|
||||
<anchor xml:id="theme-stylesheet-css"/>
|
||||
<anchor xml:id="core3.0-css"/>
|
||||
<anchor xml:id="core-Default3.0-css"/>
|
||||
<anchor xml:id="theme-Default3.0-css"/>
|
||||
<!--
|
||||
[if lt IE 9]>
|
||||
<link href="http://inprogress.baeldung.com/wp-content/themes/canvas/css/non-responsive.css" rel="stylesheet" type="text/css" />
|
||||
<style type="text/css">.col-full, #wrapper { width: 1150px; max-width: 1150px; } #inner-wrapper { padding: 0; } body.full-width #header, #nav-container, body.full-width #content, body.full-width #footer-widgets, body.full-width #footer { padding-left: 0; padding-right: 0; } body.fixed-mobile #top, body.fixed-mobile #header-container, body.fixed-mobile #footer-container, body.fixed-mobile #nav-container, body.fixed-mobile #footer-widgets-container { min-width: 1150px; padding: 0 1em; } body.full-width #content { width: auto; padding: 0 1em;}</style>
|
||||
<![endif]
|
||||
-->
|
||||
<!--
|
||||
Adjust the website width
|
||||
-->
|
||||
<!--
|
||||
Custom CSS Styling
|
||||
-->
|
||||
<!--
|
||||
Woo Shortcodes CSS
|
||||
-->
|
||||
<!--
|
||||
Custom Stylesheet
|
||||
-->
|
||||
<!--
|
||||
Theme version
|
||||
-->
|
||||
<!--
|
||||
All in one Favicon 4.3
|
||||
-->
|
||||
<!--
|
||||
All in One SEO Pack 2.2.3.1 by Michael Torbert of Semper Fi Web Design[68,148]
|
||||
-->
|
||||
<!--
|
||||
/all in one seo pack
|
||||
-->
|
||||
<anchor xml:id="wrapper"/>
|
||||
<anchor xml:id="inner-wrapper"/>
|
||||
<section>
|
||||
<title><link linkend="navigation">Navigation</link></title>
|
||||
<anchor xml:id="logo"/>
|
||||
<para><link xl:href="http://inprogress.baeldung.com/">Technical Articles</link></para>
|
||||
<para><link xl:href="http://inprogress.baeldung.com">Home</link></para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para><link xl:href="http://inprogress.baeldung.com/">Home</link></para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<!--
|
||||
/#nav
|
||||
-->
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para><link xl:href="http://inprogress.baeldung.com/?feed=rss2"/></para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<!--
|
||||
/#side-nav
|
||||
-->
|
||||
<!--
|
||||
/.menus
|
||||
-->
|
||||
<para><link linkend="top">Return to Content</link></para>
|
||||
<!--
|
||||
#content Starts
|
||||
-->
|
||||
<anchor xml:id="content"/>
|
||||
<anchor xml:id="main-sidebar-container"/>
|
||||
<!--
|
||||
#main Starts
|
||||
-->
|
||||
<para>Registration – Activate a New Account by Email</para>
|
||||
<para>By</para>
|
||||
<para><link xl:href="http://inprogress.baeldung.com/?author=10">Elena</link></para>
|
||||
<para>on</para>
|
||||
<abbrev>October 23, 2014</abbrev>
|
||||
<para>in</para>
|
||||
<para><link xl:href="http://inprogress.baeldung.com/?cat=9">Spring Security</link></para>
|
||||
<para><link xl:href=""/></para>
|
||||
<section>
|
||||
<title><emphasis role="bold">1. Overview</emphasis></title>
|
||||
<para>This article continues our ongoing <emphasis role="bold">Registration with Spring Security series</emphasis> by finishing the missing piece of the registration process – <emphasis role="bold">verifying the email to confirm the user registration</emphasis>.</para>
|
||||
<para>The registration confirmation mechanism forces the user to respond to a “<emphasis><emphasis role="bold">Confirm Registration</emphasis></emphasis>” email sent after successful registration to verify his email address and activate his account. The user does this by clicking a unique account activation link sent to him as part of the email message.</para>
|
||||
<para>Following this logic, a newly registered user will not be able to log in until email/registration verification is completed.</para>
|
||||
</section>
|
||||
<section>
|
||||
<title><emphasis role="bold">2. A Verification Token</emphasis></title>
|
||||
<para>We will make use of a simple verification token as the key artifact through which a user is verified.</para>
|
||||
<section>
|
||||
<title><emphasis role="bold">2.1. Adding a VerificationToken Entity to Our Model</emphasis></title>
|
||||
<para>The <emphasis>VerificationToken</emphasis> entity must meet the following criteria:</para>
|
||||
<orderedlist><listitem>
|
||||
<para> There will be one <emphasis>VerificationToken </emphasis>associated to a <emphasis>User</emphasis>. So, we need a one-to-one unidirectional association between the<emphasis> VerificationToken</emphasis> and the <emphasis>User</emphasis>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para> It will be created after the user registration data is persisted.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para> It will expire in 24 hours following initial registration.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para> Its value should be unique and randomly generated.</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
<para>Requirements 2 and 3 are part of the registration logic. The other two are implemented in a simple <emphasis>VerificationToken</emphasis> entity like the one in Example 2.1.:</para>
|
||||
<para><emphasis role="bold">Example 2.1.</emphasis></para>
|
||||
<screen>@Entity
|
||||
@Table
|
||||
public class VerificationToken {
|
||||
private static final int EXPIRATION = 60 * 24;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "token")
|
||||
private String token;
|
||||
|
||||
@OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "user_id")
|
||||
private User user;
|
||||
|
||||
@Column(name = "expiry_date")
|
||||
private Date expiryDate;
|
||||
|
||||
public VerificationToken() {
|
||||
super();
|
||||
}
|
||||
public VerificationToken(String token, User user) {
|
||||
super();
|
||||
this.token = token;
|
||||
this.user = user;
|
||||
this.expiryDate = calculateExpiryDate(EXPIRATION);
|
||||
this.verified = false;
|
||||
}
|
||||
private Date calculateExpiryDate(int expiryTimeInMinutes) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(new Timestamp(cal.getTime().getTime()));
|
||||
cal.add(Calendar.MINUTE, expiryTimeInMinutes);
|
||||
return new Date(cal.getTime().getTime());
|
||||
}
|
||||
|
||||
// standard getters and setters
|
||||
}</screen>
|
||||
</section>
|
||||
<section>
|
||||
<title><emphasis role="bold">2.2. Add an Enabled Flag to the User Entity</emphasis></title>
|
||||
<para>We will set the value of this flag depending on the result of the registration confirmation use case. Lets jus add the following field to our <emphasis>User</emphasis> entity for now:</para>
|
||||
<screen>@Column(name = "enabled")
|
||||
private boolean enabled;</screen>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title><emphasis role="bold">3. The Account Registration Phase</emphasis></title>
|
||||
<para>Lets add two additional pieces of business logic to the user registration use case:</para>
|
||||
<orderedlist><listitem>
|
||||
<para> Generating a <emphasis>VerificationToken</emphasis> for the user and persisting it.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para> Sending the account confirmation email message which includes a confirmation link with the <emphasis>VerificationToken’s </emphasis>value<emphasis/>as a parameter.</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
<section>
|
||||
<title><emphasis role="bold">3.1. Using Spring Event Handling to Create the Token and Send the Verification Email</emphasis></title>
|
||||
<para>These two additional pieces of logic should not be performed by the controller directly because they are “collateral” back-end tasks. The controller will publish a Spring <emphasis>ApplicationEvent</emphasis> to trigger the execution of these tasks. This is as simple as injecting an<emphasis> ApplicationEventPublishe</emphasis>r in the controller, and then using it to publish the registration completion. Example 3.1. shows this simple logic:</para>
|
||||
<para><emphasis role="bold">Example 3.1.</emphasis></para>
|
||||
<screen>@Autowired
|
||||
ApplicationEventPublisher
|
||||
@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
|
||||
public ModelAndView registerUserAccount(@ModelAttribute("user") @Valid UserDto accountDto,
|
||||
BindingResult result, WebRequest request, Errors errors) {
|
||||
User registered = new User();
|
||||
String appUrl = request.getContextPath();
|
||||
if (result.hasErrors()) {
|
||||
return new ModelAndView("registration", "user", accountDto);
|
||||
}
|
||||
registered = createUserAccount(accountDto);
|
||||
if (registered == null) {
|
||||
result.rejectValue("email", "message.regError");
|
||||
}
|
||||
eventPublisher.publishEvent(new OnRegistrationCompleteEvent(registered,
|
||||
request.getLocale(), appUrl));
|
||||
return new ModelAndView("successRegister", "user", accountDto);
|
||||
}</screen>
|
||||
</section>
|
||||
<section>
|
||||
<title><emphasis role="bold">3.2. Spring Event Handler Implementation</emphasis></title>
|
||||
<para>The controller is using an <emphasis>ApplicationEventPublisher</emphasis> to start the <emphasis>RegistrationListener</emphasis> that will handle the verification token creation and confirmation email sending. So it needs to have access to the implementation of the following interfaces:</para>
|
||||
<orderedlist><listitem>
|
||||
<para> An <emphasis role="bold">AplicationEvent</emphasis> representing the completion of the user registration.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para> An <emphasis role="bold">ApplicationListener</emphasis> bean which will listen to the published event and proceed to do all the work.</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
<para>The beans we will create are the <emphasis>OnRegistrationCompleteEvent</emphasis> , and the <emphasis>RegistrationListener</emphasis> shown Examples 3.2.1 – 3.2.2.</para>
|
||||
<para><emphasis role="bold">Example 3.2.1.</emphasis> – The <emphasis>OnRegistrationCompleteEvent </emphasis></para>
|
||||
<screen>@SuppressWarnings("serial")
|
||||
public class OnRegistrationCompleteEvent extends ApplicationEvent {
|
||||
private final String appUrl;
|
||||
private final Locale locale;
|
||||
private final User user;
|
||||
|
||||
public OnRegistrationCompleteEvent(User user, Locale locale, String appUrl) {
|
||||
super(user);
|
||||
this.user = user;
|
||||
this.locale = locale;
|
||||
this.appUrl = appUrl;
|
||||
}
|
||||
|
||||
// standard getters and setters
|
||||
}</screen>
|
||||
<para><emphasis role="bold">Example 3.2.2. </emphasis>- <emphasis role="bold">The RegistrationListener</emphasis> Responds to the <emphasis>OnRegistrationCompleteEvent </emphasis></para>
|
||||
<screen>@Component
|
||||
public class RegistrationListener implements ApplicationListener<OnRegistrationCompleteEvent> {
|
||||
@Autowired
|
||||
private IUserService service;
|
||||
|
||||
@Autowired
|
||||
private MessageSource messages;
|
||||
|
||||
@Autowired
|
||||
private JavaMailSender mailSender;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(OnRegistrationCompleteEvent event) {
|
||||
this.confirmRegistration(event);
|
||||
}
|
||||
|
||||
private void confirmRegistration(OnRegistrationCompleteEvent event) {
|
||||
User user = event.getUser();
|
||||
String token = UUID.randomUUID().toString();
|
||||
service.addVerificationToken(user, token);
|
||||
String recipientAddress = user.getEmail();
|
||||
String subject = "Registration Confirmation";
|
||||
String confirmationUrl = event.getAppUrl() + "/regitrationConfirm.html?token=" + token;
|
||||
String message = messages.getMessage("message.regSucc", null, event.getLocale());
|
||||
SimpleMailMessage email = new SimpleMailMessage();
|
||||
email.setTo(recipientAddress);
|
||||
email.setSubject(subject);
|
||||
email.setText(message + " \r\n" + "http://localhost:8080" + confirmationUrl);
|
||||
mailSender.send(email);
|
||||
}
|
||||
}</screen>
|
||||
<para>Here, the <emphasis>confirmRegistration</emphasis> method will receive the <emphasis>OnRegistrationCompleteEvent</emphasis>, extract all the necessary <emphasis>User</emphasis> information from it, create the verification token, persist it, and then send it as a parameter in the “Confirm Registration” link sent to the user.</para>
|
||||
</section>
|
||||
<section>
|
||||
<title><emphasis role="bold">3.3. Processing the Verification Token Parameter</emphasis></title>
|
||||
<para>When the user receives the “Confirm Registration” email, he will click on the attached link and fire a GET request. The controller will extract the value of the token parameter in the GET request and will use it to verify the user. Lets see this logic in Example 3.3.1.</para>
|
||||
<para><emphasis role="bold">Example 3.3.1. – RegistrationController Processing the Registration Confirmation Link</emphasis></para>
|
||||
<screen>private IUserService service;
|
||||
|
||||
@Autowired
|
||||
public RegistrationController(IUserService service){
|
||||
this.service = service
|
||||
}
|
||||
@RequestMapping(value = "/regitrationConfirm", method = RequestMethod.GET)
|
||||
public String confirmRegistration(WebRequest request, Model model,
|
||||
@RequestParam("token") String token) {
|
||||
VerificationToken verificationToken = service.getVerificationToken(token);
|
||||
if (verificationToken == null) {
|
||||
model.addAttribute("message", messages.getMessage("auth.message.invalidToken",
|
||||
null, request.getLocale()));
|
||||
return "redirect:/badUser.html?lang=" + request.getLocale().getLanguage();
|
||||
}
|
||||
User user = verificationToken.getUser();
|
||||
Calendar cal = Calendar.getInstance();
|
||||
if (user == null) {
|
||||
model.addAttribute("message", messages.getMessage("auth.message.invalidUser",
|
||||
null, request.getLocale()));
|
||||
return "redirect:/badUser.html?lang=" + request.getLocale().getLanguage();
|
||||
}
|
||||
if ((verificationToken.getExpiryDate().getTime() - cal.getTime().getTime()) <= 0) {
|
||||
user.setEnabled(false);
|
||||
} else {
|
||||
user.setEnabled(true);
|
||||
}
|
||||
service.saveRegisteredUser(user);
|
||||
return "redirect:/login.html?lang=" + request.getLocale().getLanguage();
|
||||
}</screen>
|
||||
<para>Notice that if there is no user associated with the <emphasis>VerificationToken</emphasis> or if the <emphasis>VerificationToken</emphasis> does not exist, the controller will return a<emphasis> badUser.html</emphasis> page with the corresponding error message (See Example 3.3.2.).</para>
|
||||
<para><emphasis role="bold">Example 3.3.2. – The badUser.html</emphasis></para>
|
||||
<screen><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
|
||||
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
|
||||
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
|
||||
<fmt:setBundle basename="messages" />
|
||||
<%@ page session="true"%>
|
||||
<html>
|
||||
<head>
|
||||
<link href="<c:url value="/resources/bootstrap.css" />" rel="stylesheet">
|
||||
<title>Expired</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${message}</h1>
|
||||
<br>
|
||||
<a href="<c:url value="/user/registration" />">
|
||||
<spring:message code="label.form.loginSignUp"></spring:message>
|
||||
</a>
|
||||
</body>
|
||||
</html></screen>
|
||||
<para>If the token and user exist, the controller then proceeds to set the <emphasis>User</emphasis>‘s <emphasis>enabled</emphasis> field after checking if the <emphasis>VerificationToken</emphasis> has expired.</para>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title><emphasis role="bold">4. Adding Account Activation Checking to the Login Process</emphasis></title>
|
||||
<para>We need to add the following verification logic to My<emphasis>UserDetailsService’s </emphasis><emphasis role="bold">lo</emphasis><emphasis><emphasis role="bold">adUserByUsername</emphasis></emphasis> method:</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para> Make sure that the user is enabled before letting him log in.</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<para>Example 4.1. shows the simple <emphasis>isEnabled()</emphasis> check.</para>
|
||||
<para><emphasis role="bold">Example 4.1. – </emphasis>Checking the VerificationToken<emphasis role="bold"> in MyUserDetailsService</emphasis></para>
|
||||
<screen>private UserRepository userRepository;
|
||||
@Autowired
|
||||
private IUserService service;
|
||||
@Autowired
|
||||
private MessageSource messages;
|
||||
|
||||
@Autowired
|
||||
public MyUserDetailsService(UserRepository repository) {
|
||||
this.userRepository = repository;
|
||||
}
|
||||
|
||||
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||
boolean enabled = true;
|
||||
boolean accountNonExpired = true;
|
||||
boolean credentialsNonExpired = true;
|
||||
boolean accountNonLocked = true;
|
||||
try {
|
||||
User user = userRepository.findByEmail(email);
|
||||
if (user == null) {
|
||||
return new org.springframework.security.core.userdetails.User(" ", " ", enabled,
|
||||
true, true, true, getAuthorities(new Integer(1)));
|
||||
}
|
||||
if (!user.isEnabled()) {
|
||||
accountNonExpired = false;
|
||||
service.deleteUser(user);
|
||||
return new org.springframework.security.core.userdetails.User(" ", " ", enabled,
|
||||
accountNonExpired, true, true, getAuthorities(new Integer(1)));
|
||||
}
|
||||
return new org.springframework.security.core.userdetails.User(user.getEmail(),
|
||||
user.getPassword().toLowerCase(),
|
||||
enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
|
||||
getAuthorities(user.getRole().getRole()));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}</screen>
|
||||
<para>Notice that if the user is not enabled, the account is deleted and the method returns an <emphasis>org.springframework.security.core.userdetails.User</emphasis> with the <emphasis>accountNonExpired</emphasis> flag set to false. This will trigger a <emphasis role="bold">SPRING_SECURITY_LAST_EXCEPTION</emphasis> in the login process. This exception’s String value is: ‘<emphasis>User Account Has Expired</emphasis>‘.</para>
|
||||
<para>Now, we need to modify our<emphasis> login.html</emphasis> page to show this and any other exception messages resulting from en email verification error. The error checking code we added to <emphasis>login.html</emphasis> is shown in Example 4.2.<emphasis>:</emphasis></para>
|
||||
<para><emphasis role="bold">Example 4.2. – Adding Account Activation Error Checking t</emphasis>o l<emphasis>ogin.html</emphasis></para>
|
||||
<screen><c:if test="${param.error != null}">
|
||||
<c:choose>
|
||||
<c:when test="${SPRING_SECURITY_LAST_EXCEPTION.message == 'User is disabled'}">
|
||||
<div class="alert alert-error">
|
||||
<spring:message code="auth.message.disabled"></spring:message>
|
||||
</div>
|
||||
</c:when>
|
||||
<c:when test="${SPRING_SECURITY_LAST_EXCEPTION.message == 'User account has expired'}">
|
||||
<div class="alert alert-error">
|
||||
<spring:message code="auth.message.expired"></spring:message>
|
||||
</div>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<div class="alert alert-error">
|
||||
<spring:message code="message.badCredentials"></spring:message>
|
||||
</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if></screen>
|
||||
</section>
|
||||
<section>
|
||||
<title>5. Adapting the Persistence Layer</title>
|
||||
<para>We need to modify the API of the persistence layer by:</para>
|
||||
<orderedlist><listitem>
|
||||
<para> Creating a <emphasis>VerificationTokenRepository</emphasis>. For<emphasis> User</emphasis> and <emphasis>VerificationToken</emphasis> access.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para> Adding methods to the <emphasis>IUserInterface</emphasis> and its implementation for new CRUD operations needed.</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
<para>Examples 5.1 – 5.3. show the new interfaces and implementation:</para>
|
||||
<para><emphasis role="bold">Example 5.1.</emphasis> – The <emphasis>VerificationTokenRepository</emphasis></para>
|
||||
<screen>public interface VerificationTokenRepository extends JpaRepository<VerificationToken, Long> {
|
||||
|
||||
VerificationToken findByToken(String token);
|
||||
|
||||
VerificationToken findByUser(User user);
|
||||
}</screen>
|
||||
<para><emphasis role="bold">Example 5.2.</emphasis> – The <emphasis>IUserService</emphasis> Interface</para>
|
||||
<screen>public interface IUserService {
|
||||
|
||||
User registerNewUserAccount(UserDto accountDto) throws EmailExistsException;
|
||||
|
||||
User getUser(String verificationToken);
|
||||
|
||||
void saveRegisteredUser(User user);
|
||||
|
||||
void addVerificationToken(User user, String token);
|
||||
|
||||
VerificationToken getVerificationToken(String VerificationToken);
|
||||
|
||||
void deleteUser(User user);
|
||||
}</screen>
|
||||
<para><emphasis role="bold">Example 5.3.</emphasis> The <emphasis>UserService </emphasis></para>
|
||||
<screen>@Service
|
||||
public class UserService implements IUserService {
|
||||
@Autowired
|
||||
private UserRepository repository;
|
||||
|
||||
@Autowired
|
||||
private VerificationTokenRepository tokenRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException {
|
||||
if (emailExist(accountDto.getEmail())) {
|
||||
throw new EmailExistsException("There is an account with that email adress: " +
|
||||
accountDto.getEmail());
|
||||
}
|
||||
User user = new User();
|
||||
user.setFirstName(accountDto.getFirstName());
|
||||
user.setLastName(accountDto.getLastName());
|
||||
user.setPassword(accountDto.getPassword());
|
||||
user.setEmail(accountDto.getEmail());
|
||||
user.setRole(new Role(Integer.valueOf(1), user));
|
||||
return repository.save(user);
|
||||
}
|
||||
|
||||
private boolean emailExist(String email) {
|
||||
User user = repository.findByEmail(email);
|
||||
if (user != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUser(String verificationToken) {
|
||||
User user = tokenRepository.findByToken(verificationToken).getUser();
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerificationToken getVerificationToken(String VerificationToken) {
|
||||
return tokenRepository.findByToken(VerificationToken);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void saveRegisteredUser(User user) {
|
||||
repository.save(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void deleteUser(User user) {
|
||||
repository.delete(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void addVerificationToken(User user, String token) {
|
||||
VerificationToken myToken = new VerificationToken(token, user);
|
||||
tokenRepository.save(myToken);
|
||||
}
|
||||
}</screen>
|
||||
</section>
|
||||
<section>
|
||||
<title>6. Conclusion</title>
|
||||
<para>We have expanded our Spring registration process to include an email based account activation procedure. The account activation logic requires sending a verification token to the user via email, so that he can send it back to the controller to verify his identity. A <emphasis role="italic">Spring event handler layer </emphasis>takes care of the back-end work needed to send the confirmation email after the controller persists a registered.</para>
|
||||
<para><link xl:href=""/></para>
|
||||
<anchor xml:id="dd_ajax_float"/>
|
||||
<para><link xl:href="http://twitter.com/share"/></para>
|
||||
<!--
|
||||
/.entry
|
||||
-->
|
||||
<section>
|
||||
<title>Subscribe</title>
|
||||
<para>Subscribe to our e-mail newsletter to receive updates.</para>
|
||||
<para><link xl:href="http://inprogress.baeldung.com/?feed=rss2"/></para>
|
||||
<!--
|
||||
col-left
|
||||
-->
|
||||
<!--
|
||||
/.post
|
||||
-->
|
||||
<para><link xl:href="http://inprogress.baeldung.com/?p=653"><emphasis role="italic"/> (published) Handling Static Resources With Spring</link></para>
|
||||
<para><link xl:href="http://inprogress.baeldung.com/?p=1430">Convert HTML to PDF using Apache FOP <emphasis role="italic"/></link></para>
|
||||
<anchor xml:id="comments"/>
|
||||
<section>
|
||||
<title>No comments yet.</title>
|
||||
<anchor xml:id="respond"/>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="reply-title.1">
|
||||
<title>Leave a Reply <link xl:href="/?p=1092#respond">Click here to cancel reply.</link></title>
|
||||
<anchor xml:id="commentform"/>
|
||||
<para>Logged in as <link xl:href="http://inprogress.baeldung.com/wp-admin/profile.php">odeskAuthor8</link>. <link xl:href="http://inprogress.baeldung.com/wp-login.php?action=logout">Log out?</link></para>
|
||||
<para>Comment<anchor xml:id="comment"/></para>
|
||||
<para><anchor xml:id="submit"/><anchor xml:id="comment_post_ID"/><anchor xml:id="comment_parent"/></para>
|
||||
<!--
|
||||
#respond
|
||||
-->
|
||||
<!--
|
||||
/#main
|
||||
-->
|
||||
<!--
|
||||
/#main-sidebar-container
|
||||
-->
|
||||
<!--
|
||||
/#content
|
||||
-->
|
||||
<anchor xml:id="copyright"/>
|
||||
<para>© 2014 Technical Articles. All Rights Reserved. </para>
|
||||
<anchor xml:id="credit"/>
|
||||
<!--
|
||||
/#inner-wrapper
|
||||
-->
|
||||
<!--
|
||||
/#wrapper
|
||||
-->
|
||||
<!--
|
||||
/.fix
|
||||
-->
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</article>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue